逆向解释

Sta*_* R. 36 c# covariance contravariance c#-4.0

首先,我对SO和有关协变和逆变和一个大感谢博客出去看了很多解释埃里克利珀用于生产在这样一个伟大的系列赛协变和逆变.

但是我有一个更具体的问题,我试图让我的头脑稍微偏执一点.

据我所知,根据埃里克的解释,协方差和反方差都是描述转换的形容词.协变变换是保留类型顺序的变换,逆变变换是逆转变换的变换.

我理解协方差,我认为大多数开发人员直观地理解.

//covariant operation
Animal someAnimal = new Giraffe(); 
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal(); 
Run Code Online (Sandbox Code Playgroud)

这里的返回操作是协变的,因为我们保留了动物仍然比哺乳动物或长颈鹿大的大小.在这方面,大多数返回操作都是协变的,逆变操作是没有意义的.

  //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(); 
Run Code Online (Sandbox Code Playgroud)

这段代码当然对大多数开发人员没有意义.

我的困惑在于Contravariant参数参数.如果你有一个方法,如

bool Compare(Mammal mammal1, Mammal mammal2);
Run Code Online (Sandbox Code Playgroud)

我一直都知道输入参数总是强制逆变行为.这样如果类型被用作输入参数,则其行为应该是逆变的.

但是以下代码之间有什么区别

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?
Run Code Online (Sandbox Code Playgroud)

出于同样的原因,你无法做到这样的事情,你做不到

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

   //not valid
   Compare(new Animal(), new Dolphin());
Run Code Online (Sandbox Code Playgroud)

我想我要问的是,是什么让方法参数通过逆变换.

对不起,很长的帖子,也许我不明白这个.

编辑:

根据下面的一些对话,我理解,例如使用委托层可以清楚地显示逆转.请考虑以下示例

//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;
Run Code Online (Sandbox Code Playgroud)

当然这是非法的,因为有人可以将任何动物传递给someAction,因为ProcessMammal期望任何哺乳动物或更具特异性的东西(比哺乳动物小).这就是为什么someAction必须只是Action或更具体的东西(Action)

然而,这是在中间引入一层代表,是否有必要为了进行逆变投影,中间必须有代表?如果我们将Process定义为接口,我们会将参数参数声明为逆变类型,因为我们不希望有人能够通过代理执行上面显示的操作吗?

public interface IProcess<out T>
{
    void Process(T val);
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*ina 27

更新:哎呀.事实证明,我在最初的答案中混淆了方差和"赋值兼容性".相应地编辑了答案.我也写了一篇博文,希望能更好地回答这些问题:协方差和反方差常见问题解答

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

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
Run Code Online (Sandbox Code Playgroud)

此外,你甚至没有协方差.您所拥有的称为"赋值兼容性",这意味着您始终可以将更多派生类型的实例分配给派生类型较少的实例.

在C#中,数组,委托和通用接口支持variance.正如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<Person>,
    // although the method expects IEqualityComparer<Employee>.
    IEnumerable<Employee> noduplicates =
        employees.Distinct<Employee>(new PersonComparer());
    
    Run Code Online (Sandbox Code Playgroud)
  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;
     }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 对Func和Action Generic代表使用Variance

     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;
    
    Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.


Dav*_* O. 14

在实例化课程时,协方差和反演不是你可以观察到的事情.因此,在查看简单的类实例化时,谈论其中一个是错误的,就像在您的示例中一样: Animal someAnimal = new Giraffe(); //covariant operation

这些术语不对操作进行分类.术语协方差,逆变和不变性描述了类的某些方面及其子类之间的关系.

协方差
意味着一个方面的变化类似于继承的方向.
逆变
意味着一个方面与继承方向相反.
不变性
表示方面不会从类更改为其子类.

在谈论Cov.,Contrav时,我们通常会考虑以下几个方面.和Inv.:

  • 方法
    • 参数类型
    • 返回类型
    • 其他与签名相关的方面,例如抛出异常.
  • 泛型

让我们看几个例子来更好地理解这些术语.

class T
class T2 extends T
 
//Covariance: The return types of the method "method" have the same
//direction of inheritance as the classes A and B.
class A { T method() }
class B extends A { T2 method() }
 
//Contravariance: The parameter types of the method "method" have a
//direction of inheritance opposite to the one of the classes A and B.
class A { method(T2 t) }
class B { method(T t) }
Run Code Online (Sandbox Code Playgroud) 在这两种情况下,"方法"都被覆盖了!此外,上述示例是Cov的唯一合法出现.和反对.面向对象语言.:

  • 协方差 - 返回类型和异常抛出语句
  • 逆变 - 输入参数
  • 不变性 - 输入和输出参数

让我们看看一些反例来更好地理解上面的列表:

//Covariance of return types: OK
class Monkey { Monkey clone() }
class Human extends Monkey { Human clone() }
 
Monkey m = new Human();
Monkey m2 = m.clone(); //You get a Human instance, which is ok,
                       //since a Human is-a Monkey.
 
//Contravariance of return types: NOT OK
class Fruit
class Orange extends Fruit
 
class KitchenRobot { Orange make() }
class Mixer extends KitchenRobot { Fruit make() }
 
KitchenRobot kr = new Mixer();
Orange o = kr.make(); //Orange expected, but got a fruit (too general!)
 
//Contravariance of parameter types: OK
class Food
class FastFood extends Food
 
class Person { eat(FastFood food) }
class FatPerson extends Person { eat(Food food) }
 
Person p = new FatPerson();
p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats.
 
//Covariance of parameter types: NOT OK
class Person { eat(Food food) }
class FatPerson extends Person { eat(FastFood food) }
 
Person p = new FatPerson();
p.eat(new Food()); //Oops! FastFood expected, but got Food (too general).
Run Code Online (Sandbox Code Playgroud)

这个话题非常复杂,我可以继续这么长时间.我建议你检查一下Cov.和反对.你自己的泛型.此外,您需要知道动态绑定如何工作以完全理解示例(哪些方法得到完全调用).

这些术语源于Liskov替换原则,该原则定义了将数据类型建模为另一个子类型的必要标准.您可能还想调查它.


Lee*_*Lee 10

我的理解是,不是共型/反型变体的子类型关系,而是这些类型之间的操作(或投影)(例如委托和泛型).因此:

Animal someAnimal = new Giraffe();
Run Code Online (Sandbox Code Playgroud)

不是共变体,而是这只是赋值兼容性,因为Giraffe类型比"Animal"类型"小".当您在这些类型之间进行某些投影时,Co/contra-variance会成为一个问题,例如:

IEnumerable<Giraffe> giraffes = new[] { new Giraffe() };
IEnumerable<Animal> animals = giraffes;
Run Code Online (Sandbox Code Playgroud)

这在C#3中无效,但它应该是可能的,因为一系列长颈鹿是一系列动物.投影T -> IEnumerable<T>保留由于类型的关系中的"方向" Giraffe < AnimalIEnumerable<Giraffe> < IEnumerable<Animal>(请注意,分配要求左侧的类型是至少一样宽的右侧).

Contra-variance扭转了类型关系:

Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> printGiraffe = printAnimal;
Run Code Online (Sandbox Code Playgroud)

这在C#3中也是不合法的,但它应该是因为任何采取动物的动作都可以应对通过长颈鹿.但是,由于Giraffe < AnimalAction<Animal> < Action<Giraffe>投影已经颠倒了类型关系.这在C#4中是合法的.

所以回答你的例子中的问题:

//the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility
Mammal mammal1 = new Giraffe();
Mammal mammal2 = new Dolphin();

//compare is contravariant with respect to its arguments - 
//the delegate assignment is legal in C#4 but not in C#3
Func<Mammal, Mammal, bool> compare = (m1, m2) => //whatever
Func<Giraffe, Dolphin, bool> c2 = compare;

//always invalid - right hand side must be smaller or equal to left hand side
Mammal mammal1 = new Animal();

//not valid for same reason - animal cannot be assigned to Mammal
Compare(new Animal(), new Dolphin());
Run Code Online (Sandbox Code Playgroud)