(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>
nó 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.
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. –
@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. –
@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 :) –