Во-первых, я прочитал много объяснений о 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);
}