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

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

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

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

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

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

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

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

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

Ответ 1

Обновление: Упс. Как оказалось, я перепутал дисперсию и "совместимость присваивания" в своем первоначальном ответе. Отредактировал ответ соответственно. Также я написал пост в блоге, который, я надеюсь, должен лучше ответить на такие вопросы: Covariance and Contravariance 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

Кроме того, у вас даже нет ковариации здесь. То, что у вас есть, называется "совместимость присваивания", что означает, что вы всегда можете назначить экземпляр более производного типа экземпляру менее производного типа.

В С# поддерживается дисперсия для массивов, делегатов и универсальных интерфейсов. Как сказал Эрик Липперт в своем блоге, в чем разница между ковариацией и совместимостью заданий? в том, что лучше рассматривать дисперсию как "проекцию" типов.

Ковариантность легче понять, поскольку она следует правилам совместимости присваивания (массив более производного типа может быть назначен массиву менее производного типа, "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 и Generic

     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;
    

Надеюсь это поможет.

Ответ 2

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

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

<дл > <Дт > ковариациядт >

означает, что аспект изменяется аналогично направлению наследования. <Дт > контрвариациядт > означает, что аспект изменяется против направления наследования. <Дт > инвариантностьдт > означает, что аспект не изменяется от класса к его подклассу (es). Дл >

Мы обычно рассматриваем следующие аспекты, когда говорим о Cov., 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. из Generics. Кроме того, вам нужно знать, как работает динамическое связывание, чтобы полностью понять примеры (какие методы точно вызывают).

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

Ответ 3

Насколько я понимаю, это не отношения подтипов, которые являются ко-противоположными, а скорее операции (или проекции) между этими типами (такими как делегаты и обобщения). Следовательно:

Animal someAnimal = new Giraffe();

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

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

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

Противопоставление меняет отношение типа:

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

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

введите описание изображения здесь

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