逆变量的常见编程用法是什么?

Sho*_*kie 7 c# c++ java covariance contravariance

我已经阅读了以下有关逆差异的帖子和Lasse V. Karlsen的回答:

理解C#中的Covariant和Contravariant接口

即使我理解这个概念,我也不明白为什么它有用.例如,为什么有人会制作一个只读列表(如帖子中所示List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>)

我也知道,重写方法的参数可能是矛盾的(从概念上讲.据我所知,这在C#,Java和C++中没有使用).这有什么例子才有意义?

我会欣赏一些简单的现实世界的例子.

Mat*_*son 4

(我认为这个问题更多的是关于协方差而不是逆变,因为引用的例子与协方差有关。)

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

脱离上下文,这有点误导。在您引用的示例中,作者意图传达这样的想法:从技术上讲,如果 theList<Fish>实际上引用了 a,List<Animal>则向其添加 aFish安全的。

但当然,这也会让你添加一个Cow- 这显然是错误的。

因此编译器不允许您将List<Animal>引用分配给List<Fish>引用。

那么什么时候这才真正安全且有用呢?

如果集合不能被修改,那么这是一个安全的赋值。在 C# 中,anIEnumerable<T>可以表示不可修改的集合。

所以你可以安全地执行此操作:

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

因为不可能将非 Fish 添加到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;
Run Code Online (Sandbox Code Playgroud)

逆变

逆变的一个使用示例是,当您想要通过基类类型对项目或项目集合应用某些操作时,即使您有派生类型也是如此。

例如,您可以有一个方法,按如下顺序对每个项目应用操作StockItem

void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action)
{
    foreach (var item in items)
        action(item);
}
Run Code Online (Sandbox Code Playgroud)

使用该StockItem示例,我们假设它有一个Print()方法,您可以使用该方法将其打印到收银机收据上。您可以调用然后像这样使用它:

Action<StockItem> printItem = item => { item.Print(); }
ApplyToStockItems(shopper.Basket, printItem);
Run Code Online (Sandbox Code Playgroud)

在此示例中,购物篮中的商品类型可能是Fruit、、等等。但因为它们都派生自,所以该代码适用于所有它们。ElectronicsClothingStockItem

希望这种代码的用途是清楚的!这与 Linq 中许多方法的工作方式非常相似。