一尘不染

矛盾解释

c#

首先,我在SO和博客上阅读了许多有关协方差和逆方差的解释,非常感谢Eric Lippert
撰写了有关协方差和逆方差的出色文章

但是,我有一个更具体的问题,我想尽我所能。

据我对埃里克(Eric)的解释,协方差和逆方差都是形容转换的形容词。协变转换是保留类型顺序的转换,而协变转换是将其反转的转换。

我以某种方式理解协方差,以至于我认为大多数开发人员都可以直观地理解。

//covariant operation
Animal someAnimal = new Giraffe(); 
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal();

这里的返回操作是协变的,因为我们保留了两个Animal都大于哺乳动物或长颈鹿的大小。值得注意的是,大多数返回操作是协变的,而相反的操作则没有意义。

  //if return operations were contravariant
  //the following would be illegal
  //as Mammal would need to be stored in something
  //equal to or less derived than Mammal
  //which would mean that Animal is now less than or equal than Mammal
  //therefore reversing the relationship
  Animal someAnimal =  Mammal.GetSomeMammal();

对于大多数开发人员来说,这段代码当然是没有意义的。

我的困惑在于Contravariant参数参数。如果您有以下方法

bool Compare(Mammal mammal1, Mammal mammal2);

我一直了解到,输入参数始终会强制发生反行为。这样,如果将类型用作输入参数,则其行为应是对立的。

但是下面的代码有什么区别

Mammal mammal1 = new Giraffe(); //covariant
Mammal mammal2 = new Dolphin(); //covariant

Compare(mammal1, mammal2); //covariant or contravariant?
//or
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant?

同样,你不能做这样的事情,你不能做

   //not valid
   Mammal mammal1 = new Animal();

   //not valid
   Compare(new Animal(), new Dolphin());

我想我要问的是,是什么使方法参数传递了逆变换。

抱歉,很长的帖子,也许我对此理解不正确。

编辑:

通过下面的一些对话,我了解到例如使用委托层可以清楚地显示出差异。考虑下面的例子

//legal, covariance
Mammal someMammal = new Mammal();
Animal someAnimal = someMammal;

// legal in C# 4.0, covariance (because defined in Interface)
IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>();
IEnumerable<Animal> animalList = mammalList;

//because of this, one would assume
//that the following line is legal as well

void ProcessMammal(Mammal someMammal);

Action<Mammal> processMethod = ProcessMammal;
Action<Animal> someAction = processMethod;

当然这是非法的,因为有人可以将任何Animal传递给someAction,因为ProcessMammal期望的是哺乳动物或更具体的事物(小于Mammal)。这就是为什么someAction只能是Action或更具体的内容(Action)的原因

但是,这在中间引入了一层代表,是否有必要进行中间投影才能发生逆投影?而且,如果我们将Process定义为接口,则仅将参数参数声明为反类型,这是因为我们不希望有人能够使用委托执行我上面显示的操作吗?

public interface IProcess<out T>
{
    void Process(T val);
}

阅读 475

收藏
2020-05-19

共1个答案

一尘不染

更新:
糟糕。事实证明,我在最初的回答中混合了方差和“分配兼容性”。相应地编辑了答案。我也写了一篇博客文章,希望可以更好地回答这样的问题:协方差和协方差常见问题

答: 我想您第一个问题的答案是,在此示例中您没有矛盾:

bool Compare(Mammal mammal1, Mammal mammal2); 
Mammal mammal1 = new Giraffe(); //covariant - no             
Mammal mammal2 = new Dolphin(); //covariant - no

Compare(mammal1, mammal2); //covariant or contravariant? - neither            
//or             
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither

此外,这里甚至没有协方差。您拥有的称为“分配兼容性”,这意味着您始终可以将派生程度更高的实例分配给派生程度较小的实例。

在C#中,数组,委托和通用接口均支持方差。正如埃里克·利珀特(Eric
Lippert)在其博客文章中所说的那样,协方差和赋值兼容性之间有何区别?最好将方差视为类型的“投影”。

协方差更容易理解,因为它遵循分配兼容性规则(可以将派生类型更多的数组分配给派生类型较少的数组“ object [] objs = new string
[10];”)。逆差逆转了这些规则。例如,假设您可以执行类似“ string [] strings = new object
[10];”的操作。当然,由于明显的原因,您不能执行此操作。但这将是协变的(但同样,数组不是协变的,它们仅支持协方差)。

以下是MSDN的示例,希望可以向您展示矛盾的真正含义(我现在拥有这些文档,因此,如果您认为文档中尚不清楚,请随时给我反馈):

  1. 在通用集合的接口中使用差异

    Employee[] employees = new Employee[3];
    

    // You can pass PersonComparer,
    // which implements IEqualityComparer,
    // although the method expects IEqualityComparer.
    IEnumerable noduplicates =
    employees.Distinct(new PersonComparer());

  2. 在代表中使用差异

    // Event hander that accepts a parameter of the EventArgs type.
    

    private void MultiHandler(object sender, System.EventArgs e)
    {
    label1.Text = System.DateTime.Now.ToString();
    }
    public Form1()
    {
    InitializeComponent();
    // You can use a method that has an EventArgs parameter,
    // although the event expects the KeyEventArgs parameter.
    this.button1.KeyDown += this.MultiHandler;
    // You can use the same method
    // for an event that expects the MouseEventArgs parameter.
    this.button1.MouseClick += this.MultiHandler;
    }

  3. 将方差用于函数和动作通用委托

     static void AddToContacts(Person person)
    

    {
    // This method adds a Person object
    // to a contact list.
    }

    // The Action delegate expects
    // a method that has an Employee parameter,
    // but you can assign it a method that has a Person parameter
    // because Employee derives from Person.
    Action addEmployeeToContacts = AddToContacts;

希望这可以帮助。

2020-05-19