Во-первых, я прочитал много объяснений SO и блогов о ковариации и контравариантности, и большое спасибо Эрику Липперту за создание такой великолепной серии статей о ковариации и контравариантности .
Однако у меня есть более конкретный вопрос, который я пытаюсь немного осмыслить.
Насколько я понимаю по Объяснение Эрика состоит в том, что Ковариация и Контравариантность - прилагательные, описывающие трансформацию. Ковариантное преобразование - это преобразование, которое сохраняет порядок типов, а контравариантное преобразование - преобразование, которое меняет его на обратный.
Я понимаю ковариацию таким образом, что, как мне кажется, большинство разработчиков понимают ее интуитивно.
//covariant operation
Animal someAnimal = new Giraffe();
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal();
Операция возврата здесь ковариантна, поскольку мы сохраняем размер, при котором оба Animal все еще больше, чем Mammal или Giraffe. В этой связи большинство операций возврата ковариантны, контравариантные операции не имеют смысла.
//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());
Я предполагаю, что я спрашиваю, что заставляет аргумент метода передавать контравариантное преобразование.
Извините за длинный пост, возможно я неправильно это понимаю.
РЕДАКТИРОВАТЬ:
Из разговора ниже я понимаю, что, например, использование уровня делегата может четко показать контравариантность. Рассмотрим следующий пример
//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 ожидает чего-нибудь, что относится к Mammal или более конкретному (меньше, чем Mammal). Вот почему someAction должно быть только Action или что-то более конкретное (Action)
Однако это вводит слой делегатов посередине, необходимо ли, чтобы для контравариантной проекции был делегат посередине? А если бы мы определили Process как интерфейс, мы бы объявили параметр аргумента как контравариантный тип только потому, что мы не хотели бы, чтобы кто-то мог делать то, что я показал выше, с делегатами?
public interface IProcess<out T>
{
void Process(T val);
}