逆变解释 [英] Contravariance explained

查看:131
本文介绍了逆变解释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

,我已阅读有关协变和逆变和一个大感谢这么多的解释和博客先出去埃里克利珀,获取有关协变和逆变产生这样一个伟大的系列。

不过,我有我试图让我的头周围的一点点更具体的问题。

至于我每<一个理解

href=\"http://blogs.msdn.com/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx\">Eric's解释是协变和逆变都是形容词,描述一个转变。协改造是其中preserves类型的顺序和逆变转型是一个颠倒了。

我理解这样的方式协方差,我认为大多数开发者直观地了解

  //协变操作
动物someAnimal =新长颈鹿();
//假设回报哺乳动物,也是协变操作
someAnimal = Mammal.GetSomeMammal();

下面的返回操作协变的,因为我们是preserving其中既有动物仍然比哺乳动物或长颈鹿更大的尺寸。关于这一点最大的回报操作协变,逆变业务将没有意义。

  //如果返回行动是逆变
  //下面是非法的
  //作为哺乳动物需要被存储在事
  //等于或大于哺乳动物较少衍生
  //这将意味着动物现在小于或大于哺乳动物等于
  //因此扭转的关系
  动物someAnimal = Mammal.GetSomeMammal();

这一块,当然code会没有意义的大多数开发人员。

我的困惑在于反变参数的参数。如果你有一个方法,如

 布尔比较(哺乳动物mammal1,哺乳动物mammal2);

我一直了解到,输入参数总是强迫逆变行为。使得如果类型用作输入参数的行为应该逆变。

不过是以下code

之间的区别

 哺乳动物mammal1 =新长颈鹿(); //协变
哺乳动物mammal2 =新海豚(); //协变比较(mammal1,mammal2); //协变或逆变?
//要么
比较(新长颈鹿(),新的海豚()); //协变或逆变?

同样的道理,你不能做这样的事情,你不能这样做。

  //无效
   哺乳动物mammal1 =新的动物();   //无效
   比较(新的动物(),新的海豚());

我想什么,我问的是,是什么让传递一个逆变转换方法参数。

对不起,长的帖子,也许我理解这不正确。

编辑:

每下面的一些谈话中,我了解到,例如使用委托层能清晰显示逆变。请看下面的例子

  //法律,协方差
哺乳动物someMammal =新的哺乳动物();
动物someAnimal = someMammal;法律// C#4.0中,协方差(因为接口定义)
IEnumerable的&LT;哺乳动物GT; mammalList = Enumerable.Empty&LT;哺乳动物GT;();
IEnumerable的&LT;动物&GT; animalList = mammalList;//因为这样,人们会认为
//下面一行是合法的,以及无效ProcessMammal(哺乳动物someMammal);动作&LT;哺乳动物GT; processMethod = ProcessMammal;
动作&LT;动物&GT; someAction = processMethod;

当然,这是非法的,因为别人可以通过任何动物someAction,其中作为ProcessMammal预计多数民众赞成任何哺乳动物或更具体的(而不是哺乳动物较小)。这就是为什么someAction必须只能是行动或任何更具体的(动作)

然而,这是在中间引入代表的层,它是必要的,对于一个逆变投影到那里发生必须在中间委托?而如果我们定义流程作为一个接口,我们将宣布参数参数作为唯一的,因为我们不希望有人能够做什么,我已经表明以上代表一个逆变类型?

 公共接口IProcess&LT;出T&GT;
{
    无效处理(T VAL);
}


解决方案

更新:哎呀。事实证明,我在最初的回答混合起来方差和分配兼容性。因此编辑答案。此外,我写了一篇博客文章,我希望应该更好地回答这样的问题:<一href=\"http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx\">Covariance和逆变常见问题

答:我想回答你的第一个问题是,你没有在这个例子逆变:

 布尔比较(哺乳动物mammal1,哺乳动物mammal2);
哺乳动物mammal1 =新长颈鹿(); //协变 - 无
哺乳动物mammal2 =新海豚(); //协变 - 无比较(mammal1,mammal2); //协变或逆变? - 既不
//要么
比较(新长颈鹿(),新的海豚()); //协变或逆变? - 既不

此外,你甚至不用在这里的协方差。你有什么是所谓的分配兼容性,这意味着你可以更派生类型的实例始终分配到较少的派生类型的实例。

在C#中,方差支持数组,代表和通用接口。由于埃里克利珀在他的博客中说<一href=\"http://blogs.msdn.com/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx\">What's协方差和分配兼容性区别?是它的不如想想方差类型的投影。

协方差更容易理解,因为它遵循分配兼容性规则(更派生类型的阵列可以被分配到不太派生类型的数组,对象[]的OBJ =新的字符串[10];)。逆变逆转这些规则。例如,想象一下,你可以不喜欢的String [] =字符串新的对象[10];。当然,你不能这样做,因为显而易见的原因。但是,这将是逆变(但同样,数组不是逆变,他们支持的协方差只)。

下面是从MSDN,我希望会告诉你什么是真正的逆变意思(我自己的这些文件现在,所以如果你觉得有什么在文档不清楚,请随时给我反馈)的例子:


  1. 在接口采用方差为泛型集合

     员工[] =员工新的Employee [3];
    //你可以通过PersonComparer,
    //它实现的IEqualityComparer&LT;人&gt;中
    //虽然方法期望的IEqualityComparer&LT;员工取代。
    IEnumerable的&LT;员工&GT; noduplicates =
        employees.Distinct&LT;员工&GT;(新PersonComparer());


  2. 在代表们用方差

      //事件接受的EventArgs类型的参数弯。
    私人无效MultiHandler(对象发件人,发送System.EventArgs)
    {
       label1.Text = System.DateTime.Now.ToString();
    }
    公共Form1中()
    {
        的InitializeComponent();
        //可以使用具有一个EventArgs参数的方法,
        //虽然事件预计KeyEventArgs参数。
        this.button1.KeyDown + = this.MultiHandler;
        //你可以用同样的方法
        //对于预期的MouseEventArgs参数的事件。
        this.button1.MouseClick + = this.MultiHandler;
     }


  3. 使用方差Func键和行动泛型委托

     静态无效AddToContacts(人人)
     {
       //此方法添加一个Person对象
       //到联系人列表。
     }
     //这个动作代表希望
     //有一个Employee参数的方法,
     //但可以分配给它,有一个人参数的方法
     //因为员工从Person派生的。
     动作&LT;员工&GT; addEmployeeToContacts = AddToContacts;


希望这有助于。

First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippert for producing such a great series on Covariance and Contravariance.

However I have a more specific question that I am trying to get my head around a little bit.

As far as I understand per Eric's explanation is that Covariance and Contravariance are both adjectives that describe a transformation. Covariant transformation is that which preserves the order of types and Contravariant transformation is one that reverses it.

I understand covariance in such a manner that I think most developers understand intuitively.

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

The return operation here is covariant as we are preserving the size in which both Animal is still bigger than Mammal or Giraffe. On that note most return operations are covariant, contravariant operations would not make sense.

  //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();

This piece of code of course would not make sense to most developers.

My confusion lies in Contravariant argument parameters. If you had a method such as

bool Compare(Mammal mammal1, Mammal mammal2);

I have always learned that input parameters always force contravariant behavior. Such that if the type is used as an input parameter its behavior should be contravariant.

However what is the difference between the following code

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?

By the same token that you can't do something like this you can't do

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

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

I guess what I am asking is, what makes method argument passing a contravariant transformation.

Sorry for the long post, maybe I am understand this incorrectly.

EDIT:

Per some conversation below, I understand that for instance using a delegate layer can clearly show contravariance. Consider the following example

//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;

Of course this is illegal because someone can pass any Animal to someAction, where as the ProcessMammal expects anything thats Mammal or more specific ( lesser than Mammal ). Which is why someAction has to only be Action or anything more specific (Action)

However this is introducing a layer of delegates in the middle, is it necessary that for a contravariant projection to happen there has to be a delegate in the middle? And if we were to define Process as an interface we would declare the argument parameter as a contravariant type only because we wouldn't want someone to be able to do what I had shown above with delegates?

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

解决方案

Update: Ooops. As it turned out, I mixed up variance and "assignment compatibility" in my initial answer. Edited the answer accordingly. Also I wrote a blog post that I hope should answer such questions better: Covariance and Contravariance FAQ

Answer: I guess the answer to your first question is that you don't have contravariance in this example:

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

Furthermore, you don't even have covariance here. What you have is called "assignment compatibility", which means that you can always assign an instance of a more derived type to an instance of a less derived type.

In C#, variance is supported for arrays, delegates, and generic interfaces. As Eric Lippert said in his blog post What's the difference between covariance and assignment compatibility? is that it's better to think about variance as "projection" of types.

Covariance is easier to understand, because it follows the assignment compatibility rules (array of a more derived type can be assigned to an array of a less derived type, "object[] objs = new string[10];"). Contravariance reverses these rules. For example, imagine that you could do something like "string[] strings = new object[10];". Of course, you can't do this because of obvious reasons. But that would be contravariance (but again, arrays are not contravariant, they support covariance only).

Here are the examples from MSDN that I hope will show you what contravariance really means (I own these documents now, so if you think something is unclear in the docs, feel free to give me feedback):

  1. Using Variance in Interfaces for Generic Collections

    Employee[] employees = new Employee[3];
    // You can pass PersonComparer, 
    // which implements IEqualityComparer<Person>,
    // although the method expects IEqualityComparer<Employee>.
    IEnumerable<Employee> noduplicates =
        employees.Distinct<Employee>(new PersonComparer());
    

  2. Using Variance in Delegates

    // 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. Using Variance for Func and Action Generic Delegates

     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<Employee> addEmployeeToContacts = AddToContacts;
    

Hope this helps.

这篇关于逆变解释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆