什么';这是反方差的一种常用编程方法

本文关键字:一种 常用 方法 编程 方差 什么 | 更新日期: 2023-09-27 18:36:10

我已经阅读了以下关于反方差和Lasse V.Karlsen的答案的帖子:

理解C#中的协变和逆变接口

尽管我理解这个概念,但我不明白为什么它有用。例如,为什么有人会制作只读列表(如帖子中的List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>(

我也知道重写方法的参数可以是反方差的(从概念上讲。据我所知,这在C#、Java和C++中没有使用(。有哪些例子可以说明这一点?

我很欣赏一些简单的现实世界的例子。

什么';这是反方差的一种常用编程方法

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

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

断章取义,这有点误导。在你引用的例子中,作者打算传达这样一个想法,即从技术上讲,如果List<Fish>实际上引用了List<Animal>,那么在其中添加Fish是安全的。

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

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

那么,什么时候这才是真正安全和有用的呢?

如果不能修改集合,则这是一个安全的分配。在C#中,IEnumerable<T>可以表示不可修改的集合。

所以你可以安全地做到这一点:

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

因为不可能向CCD_ 11添加非Fish。它没有允许你这样做的方法。

那么这什么时候有用呢

当您想要访问某个集合的某些公共方法或属性时,这一点非常有用,该集合可以包含从基类派生的一个或多个类型的项。

例如,您可能有一个层次结构,代表超市的不同库存类别。

假设基类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);

在这个例子中,购物篮中的项目类型可能是FruitElectronicsClothing等等。但由于它们都源自StockItem,所以代码可以处理所有这些项目。

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

协方差对于只读(输出(存储库很有用;只写(in(存储库的差异。

public interface IReadRepository<out TVehicle>
{
    TVehicle GetItem(Guid id);
}
public interface IWriteRepository<in TVehicle>
{
    void AddItem(TVehicle vehicle);
}

这样,IReadRepository<Car>的实例也是IReadRepository<Vehicle>的实例,因为如果你从存储库中取出一辆车,它也是一辆车;然而,IWriteRepository<Vehicle>的实例也是IWriteRepository<Car>的实例,因为如果可以将车辆添加到存储库中,那么就可以将汽车写入存储库。

这解释了outin关键字用于协方差和反方差背后的推理。

至于为什么要进行这种分离(使用单独的只读和只读接口,这很可能由同一个具体类实现(,这有助于在命令(写操作(和查询(读操作(之间保持干净的分离,这可能对性能、一致性和可用性有不同的要求,因此需要在代码库中使用不同的策略。

如果你没有真正理解方差的概念,那么类似的问题可能(也将(发生。

让我们构建一个最简单的例子:

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"); }
}

好的,这是一个反方差的例子

 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);
    }
}   

在.Net 4.0之前,不允许使用ContraForInterfaces方法(所以他们实际上修复了一个BUG:(。

好的,现在ContraForInterfaces方法的含义是什么?很简单,一旦你列出了欧洲人的列表,其中的对象总是欧洲人,即使你把它们传递给一个采用IEnumerable<>的方法。通过这种方式,您将始终为对象调用正确的方法。协方差和反方差只是参数多态性(仅此而已(现在也应用于委托和接口。:(