Ответ 1
Может кто-нибудь объяснить использование части T с примером?
Конечно. IEnumerable<T>
является ковариантным. Это означает, что вы можете сделать это:
static void FeedAll(IEnumerable<Animal> animals)
{
foreach(Animal animal in animals) animal.Feed();
}
...
IEnumerable<Giraffe> giraffes = GetABunchOfGiraffes();
FeedAll(giraffes);
"Ковариант" означает, что отношение совместимости присваивания аргумента типа сохраняется в родовом типе. Giraffe
- это присвоение, совместимое с Animal
, и поэтому это отношение сохраняется в построенных типах: IEnumerable<Giraffe>
- это присвоение, совместимое с IEnumerable<Animal>
.
Почему применимо только для интерфейсов и делегатов, а не для классов?
Проблема с классами состоит в том, что классы имеют тенденцию иметь изменяемые поля. Возьмем пример. Предположим, что мы допустили это:
class C<out T>
{
private T t;
Хорошо, теперь подумайте над этим вопросом, прежде чем продолжить. Может ли C<T>
использовать какой-либо метод вне конструктора, который устанавливает поле t
в значение, отличное от его значения по умолчанию?
Поскольку он должен быть typeafe, C<T>
теперь не может иметь методов, которые принимают T как аргумент; T может быть возвращен только. Итак, кто устанавливает t, и где они получают значение, которое они устанавливают?
Типы ковариантных классов действительно работают только в том случае, если класс неизменен. И у нас нет хорошего способа сделать неизменяемые классы на С#.
Я бы хотел, чтобы мы это сделали, но нам нужно жить с системой типа CLR, которую нам дали. Я надеюсь, что в будущем мы сможем лучше поддерживать как неизменные классы, так и ковариантные классы.
Если эта функция вас интересует, подумайте о том, чтобы прочитать мою длинную серию о том, как мы разработали и внедрили эту функцию. Начните снизу:
https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/