2013-10-20 14 views
6

Tôi đã đọc các bài sau đây trên sai Contra và câu trả lời của Lasse V. Karlsen:Cách sử dụng chương trình phổ biến của contra-variance là gì?

Understanding Covariant and Contravariant interfaces in C#

Mặc dù tôi hiểu khái niệm, tôi không hiểu tại sao nó rất hữu ích. Ví dụ: tại sao mọi người sẽ tạo danh sách chỉ đọc (Giống như trong bài đăng: List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>)

Tôi cũng biết rằng các tham số của phương pháp ghi đè có thể khác nhau (khái niệm. Điều này không được sử dụng trong C#, Java và C++ theo như tôi biết). Có những ví dụ gì trong đó điều này có ý nghĩa?

Tôi đánh giá cao một số ví dụ đơn giản về thế giới thực.

+1

Có thể nhận xét viêm, nhưng người ta có thể cho rằng chúng không hữu ích. F # ví dụ khá cố ý không hỗ trợ hoặc hiệp phương sai hoặc contravariance. –

+4

@DavidArno Nhưng trong ngữ cảnh của C#, không có sự khác biệt giữa các phương sai, sẽ không có LINQ. –

+0

@MatthewWatson, thật sao? Tôi sống và học hỏi! Tôi đã không biết linq sử dụng chúng :) –

Trả lời

2

(Tôi nghĩ rằng câu hỏi này là thêm về Hiệp phương sai chứ không phải là Contravariance, kể từ khi dụ được trích dẫn là để làm với Hiệp phương sai.)

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>

Out of bối cảnh, đây là một chút sai lệch. Trong ví dụ bạn trích dẫn, tác giả dự định truyền đạt ý tưởng về mặt kỹ thuật, nếu List<Fish> thực tế tham chiếu List<Animal>sẽ an toàn để thêm Fish vào đó.

Nhưng tất nhiên, điều đó cũng sẽ cho phép bạn thêm Cow vào nó - điều này rõ ràng là sai.

Do đó trình biên dịch không cho phép bạn chỉ định tham chiếu List<Animal> cho tham chiếu List<Fish>.

Vậy khi nào điều này thực sự an toàn - và hữu ích?

Đây là nhiệm vụ an toàn nếu không thể sửa đổi bộ sưu tập. Trong C#, một IEnumerable<T> có thể đại diện cho một bộ sưu tập không thể sửa đổi.

Vì vậy, bạn có thể làm điều này một cách an toàn:

IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>

vì không có khả năng để thêm một tổ chức phi Cá để animals. Nó không có phương pháp để cho phép bạn làm như vậy.

Vậy khi nào điều này hữu ích?

Điều này rất hữu ích bất cứ khi nào bạn muốn truy cập một số phương pháp hoặc thuộc tính chung của tập hợp có thể chứa các mục của một hoặc nhiều loại bắt nguồn từ một lớp cơ sở.

Ví dụ: bạn có thể có một hệ thống phân cấp đại diện cho các loại cổ phiếu khác nhau cho một siêu thị.

Giả sử lớp cơ sở, StockItem, có thuộc tính double SalePrice.

Giả sử bạn có phương thức, Shopper.Basket() trả về số IEnumerable<StockItem> đại diện cho các mặt hàng mà người mua sắm có trong giỏ hàng của họ. Các vật phẩm trong giỏ có thể thuộc bất kỳ loại bê tông nào có nguồn gốc từ StockItem.

Trong trường hợp đó bạn có thể thêm giá của tất cả các mục trong giỏ (Tôi đã viết longhand này mà không cần sử dụng LINQ để làm rõ những gì đang xảy ra mã thực sẽ sử dụng IEnumerable.Sum() tất nhiên.):

IEnumerable<StockItem> itemsInBasket = shopper.Basket; 

double totalCost = 0.0; 

foreach (var item in itemsInBasket) 
    totalCost += item.SalePrice; 

contravariance

một ví dụ về sử dụng contravariance là khi bạn muốn áp dụng một số hành động đến một mục hoặc tập hợp các mặt hàng thông qua một loại lớp cơ sở, mặc dù bạn có một loại có nguồn gốc.

Ví dụ, bạn có thể có một phương pháp mà áp dụng một hành động để mỗi mục trong một chuỗi các StockItem như vậy:

void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action) 
{ 
    foreach (var item in items) 
     action(item); 
} 

Sử dụng ví dụ StockItem, chúng ta hãy giả sử nó có một phương pháp Print() mà bạn có thể sử dụng để in nó lên một biên lai cho đến khi nhận được. Bạn có thể gọi sau đó sử dụng nó như thế này:

Action<StockItem> printItem = item => { item.Print(); } 
ApplyToStockItems(shopper.Basket, printItem); 

Trong ví dụ này, các loại của các mặt hàng trong giỏ có thể Fruit, Electronics, Clothing và vân vân. Nhưng vì tất cả đều lấy được từ StockItem, mã hoạt động với tất cả chúng.

Hy vọng rằng tiện ích của loại mã này là rõ ràng! Điều này rất giống với cách mà rất nhiều phương thức trong LINQ hoạt động.

1

Hiệp phương sai là hữu ích cho các kho chỉ đọc (ngoài); contravariance cho các kho chỉ ghi (in).

public interface IReadRepository<out TVehicle> 
{ 
    TVehicle GetItem(Guid id); 
} 

public interface IWriteRepository<in TVehicle> 
{ 
    void AddItem(TVehicle vehicle); 
} 

Bằng cách này, một thể hiện của IReadRepository<Car> cũng là một thể hiện của IReadRepository<Vehicle> vì nếu bạn nhận được một chiếc xe ra khỏi kho, nó cũng là một phương tiện; tuy nhiên, một thể hiện của IWriteRepository<Vehicle> cũng là một thể hiện của IWriteRepository<Car>, bởi vì nếu bạn có thể thêm một chiếc xe vào kho lưu trữ, bạn có thể viết một chiếc xe vào kho lưu trữ.

Điều này giải thích lý do đằng sau các từ khóa outin cho hiệp phương sai và đối nghịch. Đối với lý do tại sao bạn có thể muốn thực hiện tách biệt này (sử dụng các giao diện chỉ đọc và ghi riêng biệt, có thể được thực hiện bởi cùng một lớp cụ thể), điều này giúp bạn giữ một sự tách biệt rõ ràng giữa các lệnh (viết:). hoạt động) và truy vấn (hoạt động đọc), có thể sẽ có các yêu cầu khác nhau về hiệu suất, tính nhất quán và tính khả dụng, và do đó cần các chiến lược khác nhau trong codebase của bạn.

0

Nếu bạn không thực sự nắm bắt được khái niệm contravariance thì các vấn đề như thế có thể (và sẽ xảy ra).

Chúng ta hãy xây dựng các ví dụ đơn giản:

public class Human 
{ 
    virtual public void DisplayLanguage() { Console.WriteLine("I do speak a language"); } 
} 
public class Asian : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak chinesse"); } 
} 

public class European : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak romanian"); } 
} 

Ok đây là một ví dụ về sai Contra

public class test 
{ 
    static void ContraMethod(Human h) 
    { 
     h.DisplayLanguage(); 
    } 

    static void ContraForInterfaces(IEnumerable<Human> humans) 
    { 
     foreach (European euro in humans) 
     { 
      euro.DisplayLanguage(); 
     } 
    } 
    static void Main() 
    { 
     European euro = new European(); 
     test.ContraMethod(euro); 
     List<European> euroList = new List<European>() { new European(), new European(), new European() }; 

     test.ContraForInterfaces(euroList); 
    } 
} 

Trước Net 4.0 các ContraForInterfaces phương pháp không được phép (để họ thực sự cố định một BUG :)).

Ok, bây giờ ý nghĩa của phương pháp ContraForInterfaces là gì? Thật đơn giản, khi bạn đã tạo danh sách người châu Âu, các đối tượng bên trong luôn là người châu Âu, ngay cả khi bạn chuyển chúng sang một phương thức có thể sử dụng IEnumerable <>. Bằng cách này, bạn sẽ gọi allways đúng phương pháp cho đối tượng của bạn. Hiệp phương sai và ContraVariance chỉ là đa hình thông số (và không có gì khác) được áp dụng ngay bây giờ cho đại biểu và giao diện. :)

Các vấn đề liên quan