(我认为这个问题更多的是关于协方差而不是逆变,因为引用的示例与协方差有关。)
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
断章取义,这有点误导。在您引用的示例中,作者打算传达这样一种想法,即从技术上讲,如果List<Fish>
实际引用 a List<Animal>
,则将a 添加到它是安全的Fish
。
但是,当然,这也可以让您添加 a Cow
——这显然是错误的。
因此,编译器不允许您将引用分配给List<Animal>
引用List<Fish>
。
那么什么时候这实际上是安全和有用的呢?
如果集合不能被修改,这是一个安全的分配。在 C# 中,anIEnumerable<T>
可以表示不可修改的集合。
所以你可以安全地做到这一点:
IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>
因为不可能将非鱼添加到animals
. 它没有允许您这样做的方法。
那么这什么时候有用呢?
每当您想要访问可以包含从基类派生的一种或多种类型的项目的集合的某些公共方法或属性时,这很有用。
例如,您可能有一个层次结构表示超市的不同库存类别。
假设基类StockItem
, 有一个属性double SalePrice
。
假设你有一个方法,Shopper.Basket()
它返回一个IEnumerable<StockItem>
代表购物者在他们的购物篮中的物品的方法。篮子里的物品可以是任何从StockItem
.
在这种情况下,您可以添加篮子中所有物品的价格(我已经写了这个速记但没有使用 Linq 来明确发生了什么。真正IEnumerable.Sum()
的代码当然会使用):
IEnumerable<StockItem> itemsInBasket = shopper.Basket;
double totalCost = 0.0;
foreach (var item in itemsInBasket)
totalCost += item.SalePrice;
逆变
逆变的一个示例使用是当您想通过基类类型对项目或项目集合应用某些操作时,即使您有派生类型。
例如,您可以有一个方法,它按StockItem
如下顺序对每个项目应用一个动作:
void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action)
{
foreach (var item in items)
action(item);
}
使用该StockItem
示例,假设它有一个Print()
方法,您可以使用该方法将其打印到收据上。您可以调用然后像这样使用它:
Action<StockItem> printItem = item => { item.Print(); }
ApplyToStockItems(shopper.Basket, printItem);
在此示例中,篮子中的项目类型可能是Fruit
、Electronics
等Clothing
。但是因为它们都派生自StockItem
,所以代码适用于所有这些。
希望这种代码的实用性很明确!这与 Linq 中许多方法的工作方式非常相似。