Очень простое и понятное объяснение «вариантности» дженерик-коллекций было, кажется, у Рихтера (или я это в каком-то блоге встречал, не помню уже):
Каждая дженерик-коллекция по параметрам дженерика на этапе компиляции разворачивается свой уникальный тип.
По сути для виртуальной машины .NET не существует типа List
When a method that uses generic type parameters is JIT-compiled, the CLR takes the method's
IL, substitutes the specified type arguments, and then creates native code that is specific to that
method operating on the specified data types. This is exactly what you want and is one of the
main features of generics. However, there is a downside to this: the CLR keeps generating native
code for every method/type combination. This is referred to as code explosion. This can end up
increasing the application's working set substantially, thereby hurting performance.
И все же итоговый результат — формируется отдельный код для методов, только не самим компилятором (он действительно компилирует дженерик в IL код именно как неспецифицированный класс), а JIT. И как результат — native код для типов двух Generic реализаций, даже специфицированных полиморфными типами — разный.
К конкретному объяснению причин отсутствия ковариантности в ранних C# это имеет не близкое отношение.
В процессе изучения, кстати возник интересный вопрос и ответ на него:
Какое поведение будет у двух экземпляров, например List, когда они будут загружены в разные домены и из одного домена будет обращение к другому?
Если я правильно понял, то при кросс-доменом обращении объект-прокси сообразит что у собственного Listи у того что в соседнем домене (к которому он и проксирует) одинаковые экземплярные члены и разрешит присвоение Listиз другого домена к собственному List.
Хотя это еще надо проверить, у Рихтера все же в примере простые типы, не generic.
ой: о)
Если используется out то один тип может быть заменён на более общий (List<string> заменяется на List<object>)
С in всё наоборот. Если используется in то один тип может быть заменён на более конкретный (void Action(object) заменяется на void Action(string))
C# 4.0 и вариантность