Контравариантность объяснена

во-первых, я прочитал много объяснений на SO и блогах о ковариации и контравариации, и большое спасибо Эрик Липперт для производства такой большой серии на ковариация и Контравариация.

однако у меня есть более конкретный вопрос, который я пытаюсь немного разобраться.

насколько я понимаю per Эрик объяснение является ли ковариация и Контравариация прилагательными это описывает трансформацию. Ковариантное преобразование-это то, что сохраняет порядок типов, А Контравариантное преобразование-то, что его отменяет.

Я понимаю ковариацию таким образом, что я думаю, что большинство разработчиков понимают интуитивно.

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

операция возврата здесь ковариантна, поскольку мы сохраняем размер, в котором оба животного все еще больше, чем млекопитающее или жираф. На этой ноте большинство операций возврата являются ковариантными, контравариантными операции не имели бы смысла.

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

этот кусок кода, конечно, не будет иметь смысла для большинства разработчиков.

моя путаница заключается в контравариантных параметрах аргумента. Если бы у вас был такой метод, как

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

Я думаю, что я спрашиваю, что делает аргумент метода проходящим контравариантное преобразование.

извините за длинный пост, возможно, я понимаю это неправильно.

EDIT:

в каком-то разговоре ниже я понимаю, что, например, использование слоя делегата может ясно показать контравариантность. Рассмотрим следующий пример

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

конечно, это незаконно, потому что кто-то может передать любое животное в какое-то действие, где в качестве ProcessMammal ожидает все, что млекопитающее или более конкретное ( меньше, чем млекопитающее ). Вот почему какое-то действие должно быть только действием или чем-то более конкретным (действием)

однако это вводит слой делегатов в середине, необходимо ли, чтобы для контравариантной проекции произошел делегат в середина? И если бы мы определили процесс как интерфейс, мы объявили бы параметр аргумента как контравариантный тип только потому, что мы не хотели бы, чтобы кто-то мог делать то, что я показал выше с делегатами?

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

5 ответов


обновление: Упс. Как оказалось, я перепутал дисперсию и" совместимость назначения " в своем первоначальном ответе. Отредактировал ответ соответственно. Также я написал пост в блоге, который надеюсь лучше ответить на такие вопросы:ковариация и Контравариация FAQ

ответ: Я думаю, ответ на ваш первый вопрос заключается в том, что у вас нет контравариантности в этом примере:

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# дисперсия поддерживается для массивов, делегатов и универсальных интерфейсов. Как сказал Эрик Липперт в своем блоге в чем разница между ковариацией и совместимостью назначения? это то, что лучше думать о дисперсии как о "проекции" типов.

ковариация проще чтобы понять, потому что он следует правилам совместимости назначения (массив более производного типа может быть назначен массиву менее производного типа, "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());
    
  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. использование дисперсии для Func и действие общие делегаты

     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;
    

надеюсь, что это помогает.


ковариация и Контравариация-это не то, что вы можете наблюдать при создании экземпляров классов. Таким образом, неправильно говорить об одном из них, глядя на простой экземпляр класса, как в вашем примере: Animal someAnimal = new Giraffe(); //covariant operation

эти термины не классифицируют операции. термины ковариация, Контравариация и инвариантность описывают отношения между определенными аспекты классов и их подклассов.

Ковариационный означает, что аспект меняется аналогично направлению наследования. Контравариантность означает, что аспект меняется в противоположную сторону от направления наследования. Инвариантность означает, что аспект не изменяется от класса к его подклассу(подклассам).

мы вообще рассматриваем следующие аспекты, когда говорим о ков., Contrav. и инв.:

  • методы
    • типы параметров
    • типы возврата
    • другие аспекты, связанные с подписью, такие как генерировать исключения.
  • дженериков

давайте посмотрим на несколько примеров для лучшего понимания терминов.

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) }
В обоих случаях" метод " переопределяется! Далее, приведенные выше примеры являются только юридические события Cov. и Contrav. в объектно-ориентированных языках.:
  • ковариация-типы возврата и операторы исключения
  • Контравариантность - Вход параметры
  • инвариантность - входные и выходные параметры

давайте посмотрим на некоторые встречные примеры, чтобы лучше понять приведенный выше список:

//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).

эта тема настолько сложная, что я мог бы продолжать очень долго. Я советую вам проверить ков. и Contrav. дженериков самостоятельно. Кроме того, вам нужно знать, как работает динамическая привязка, чтобы полностью понять примеры (какие методы точно называются).

условия возникли из принципа подстановки Лискова, который определяет необходимые критерии для моделирования типа данных как подтипа другого. Вы также можете захотеть исследовать его.


Я понимаю, что это не отношения подтипов, которые являются Co/contra-variant, а скорее операции (или проекции) между этими типами (такими как делегаты и дженерики). Таким образом:

Animal someAnimal = new Giraffe();

не является ко-вариантом, а скорее это просто совместимость назначения, поскольку тип жирафа "меньше" типа животного. Co / contra-дисперсия становится проблемой, когда у вас есть некоторая проекция между этими типами, например:

IEnumerable<Giraffe> giraffes = new[] { new Giraffe(); };
IEnumerable<Animal> animals = giraffes;

это недопустимо в C#3, однако это должно быть возможно, так как последовательность жирафов-это последовательность животных. Проекция T -> IEnumerable<T> сохраняет "направление" отношения типа с Giraffe < Animal и IEnumerable<Giraffe> < IEnumerable<Animal> (обратите внимание, что для назначения требуется, чтобы тип левой стороны был по крайней мере таким же широким, как и правый).

Contra-дисперсия меняет отношение типа:

Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> printGiraffe = printAnimal;

это также не законно в C#3, но это должно быть, так как любое действие, принимающее животное, может справиться с тем, чтобы пройти жираф. Однако, поскольку Giraffe < Animal и Action<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());

(редактировать в ответ на комментарии)

эта статья MSDN по теме описал ковариантность и контравариантность, так как это относится к соответствующей функции к делегату. Переменная типа делегата:

public delegate bool Compare(Giraffe giraffe, Dolphin dolphin);

может (из-за контравариантности) быть заполнен функцией:

public bool Compare(Mammal mammal1, Mammal mammal2)
{
    return String.Compare(mammal1.Name, mammal2.Name) == 0;
}

из моего чтения это не связано с вызовом функции напрямую, а с сопоставлением функций с делегатами. Я не уверен, что это возможно. сводится к уровню, который вы демонстрируете, при этом отдельные переменные или назначения объектов являются контравариантными или ковариантными. Но назначение делегата использует контравариантность или ковариацию таким образом, который имеет смысл для меня в соответствии со связанной статьей. Поскольку подпись делегата содержит больше производных типов, чем фактический экземпляр, это называется "contravariance", что-то отдельное от "ковариации", в котором возвращаемый тип делегата является менее производным, чем фактический пример.


посмотрите на это так: Если у меня есть функция func, которая имеет дело с подтипом млекопитающего, формы млекопитающее m = Func(g (млекопитающее)), Я могу поменять млекопитающее с чем-то включает в себя млекопитающие, который здесь является основой животные.

с точки зрения спортивной аналогии, чтобы понять приведенное ниже изображение, вы можете поймать мяч голыми руками, как в крикете, но также возможно (и проще) поймать мяч с помощью бейсбола перчатки.

то, что вы видите слева-это ковариация, то, что вы видите внутри часть параметра контрвариантности.

enter image description here

вы можете задаться вопросом: "почему левая зеленая кривая больше красной кривой? Разве подтип, который обычно делает больше, чем базовый, не должен быть больше?" Ответ: Нет. Размер скобки обозначает разнообразие разрешенных объектов, например диаграмму Венна. Набор млекопитающих меньше, чем набор животных. Аналогично, F (млекопитающее) меньше f(животное), поскольку оно поддерживает только меньший набор объектов. (я.электронная функция, которая обрабатывает млекопитающих не обрабатывать всех животных, но функция, которая обрабатывает животные всегда могут справиться с млекопитающими). Следовательно, отношение инвертируется, поскольку f (животное) может быть передано вместо f(млекопитающее), что делает его контравариантным.