ОБНОВЛЕНИЕ: Этот вопрос лег в основу моей статьи в блоге от 3 февраля 2011 года .Спасибо за отличный вопрос!
Это законно, оно не круглое и довольно распространенное.Мне лично это не нравится.
Причины, по которым мне это не нравится:
1) Это слишком умно;как вы обнаружили, умный код труден для людей, незнакомых со сложностями системы типов, чтобы интуитивно понять.
2) Это не соответствует моей интуиции того, что «представляет» универсальный тип.Мне нравятся классы для представления категорий вещей и общие классы для представления параметризованных категорий.Для меня ясно, что «список строк» и «список чисел» - это оба вида списков, различающиеся только типом объекта в списке.Мне гораздо менее понятно, что такое «TextInput из T, где T - это TextInput из T».Не заставляйте меня думать.
3) Этот шаблон часто используется в попытке навязать ограничение в системе типов, которое на самом деле не применяется в C #.А именно этот:
abstract class Animal<T> where T : Animal<T>
{
public abstract void MakeFriends(IEnumerable<T> newFriends);
}
class Cat : Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
Идея заключается в том, что «подкласс Cat of Animal может дружить только с другими кошками».
Проблема в том, что требуемое правило на самом деле не применяется:
class Tiger: Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
Теперь Тигр может дружить с Кошками, но не с Тиграми.
Чтобы действительно выполнить эту работу в C #, вам нужно сделать что-то вроде:
abstract class Animal
{
public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}
, где "THISTYPE" - это волшебная новая языковая функция, которая означает, что "переопределяющий класс требуется длявведите свой собственный тип здесь ".
class Cat : Animal
{
public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}
class Tiger: Animal
{
// illegal!
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
К сожалению, это также небезопасно:
Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});
Если правило" животное может подружиться с любым из его видов ", тоживотное может дружить с животными.Но кошка может дружить только с кошками, а не с тиграми!Материал в позициях параметров должен быть действительным контравариантно ;в этом гипотетическом случае нам потребовалось бы ковариация , что не сработает.
Кажется, я несколько отступил.Возвращаясь к предмету этого странно повторяющегося паттерна: я пытаюсь использовать этот паттерн только для общих, легко понимаемых ситуаций, подобных тем, которые упоминались в других ответах:
class SortedList<T> where T : IComparable<T>
То есть нам нужно, чтобы каждый T был сопоставимымдля всех остальных T, если у нас есть надежда составить их отсортированный список.
Для того, чтобы на самом деле помечаться как круговые, в зависимостях должна быть истинная цикличность:
class C<T, U> where T : U where U : T
Интересная область теории типов (которая в настоящее время плохо обрабатывается компилятором C #) - это область некруглых, но бесконечных универсальных типов.Я написал детектор бесконечного типа, но он не попал в компилятор C # 4 и не является приоритетным для возможных гипотетических будущих версий компилятора.Если вы заинтересованы в некоторых примерах бесконечных типов или в некоторых случаях, когда детектор цикла C # портится, см. Мою статью на эту тему:
http://blogs.msdn.com/b/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx