Другие отметили трудность с вашим интерфейсом, заключающуюся в том, что нет никакого способа четко идентифицировать классы, которые могут работать совместно с другими элементами их собственного класса; эта трудность в некоторой степени связана с тем, что такие классы нарушают принцип подстановки Лискова; если класс принимает два объекта типа baseQ и ожидает, что один из них будет работать друг с другом, тогда LSP будет диктовать, что должен быть в состоянии заменить один из объектов baseQ производным Q. Это, в свою очередь, подразумевает, что baseQ должен работать с производным Q, а производный Q должен работать с базовым Q. В более широком смысле, любая производная baseQ должна работать с любой другой производной baseQ. Таким образом, интерфейс не является ковариантным, ни контравариантным, ни инвариантным, а скорее не является универсальным.
Если причина, по которой вы хотите использовать дженерики, заключается в том, чтобы позволить интерфейсам воздействовать на структуры без упаковки, шаблон, приведенный в ответе Фога, является хорошим. Как правило, не следует беспокоиться о наложении рефлексивных ограничений на параметры типа, поскольку цель интерфейсов состоит в том, чтобы использовать их не как ограничения, а как типы переменных или параметров, а необходимые условия могут быть наложены подпрограммой с использованием ограничений (например, VectorList<T,U> where T:IVector<T,U>
).
Кстати, я должен упомянуть, что поведение типов интерфейса, используемых в качестве ограничений, очень отличается от поведения переменных и параметров типа интерфейса. Для каждого типа структуры существует другой тип, производный от ValueType; последний тип будет демонстрировать ссылочную семантику, а не семантику значений. Если переменная или параметр типа значения передается подпрограмме или сохраняется в переменной, для которой требуется тип класса, система скопирует содержимое в новый объект класса, производный от ValueType. Если рассматриваемая структура является неизменной, любая и все такие копии всегда будут содержать то же содержимое, что и оригинал и друг друга, и, таким образом, могут рассматриваться как семантически эквивалентные оригиналу. Однако, если рассматриваемая структура является изменчивой, такие операции копирования могут дать семантику, очень отличающуюся от ожидаемой. Хотя иногда бывает полезно, чтобы методы интерфейса изменяли структуры, такие интерфейсы должны использоваться с особой осторожностью.
Например, рассмотрим поведение List<T>.Enumerator
, которое реализует IEnumerator<T>
. Копирование одной переменной типа List<T>.Enumerator
в другую того же типа сделает «снимок» позиции списка; вызов MoveNext для одной переменной не повлияет на другую. Копирование такой переменной в один из типов Object
, IEnumerator<T>
или в интерфейс, производный от IEnumerator<T>
, также будет выполнять генерацию шага, и, как указано выше, вызов MoveNext для оригинальной или новой переменной не повлияет на другую. С другой стороны, копирование одной переменной типа Object
, IEnumerator<T>
или интерфейса, производного от IEnumerator<T>
, в другой тип, также являющийся одним из этих типов (одинаковых или разных), не сделает снимок, а просто скопирует ссылка на ранее созданный снимок.
Иногда бывает полезно, чтобы все копии переменной были семантически эквивалентными. В других случаях может быть полезно, чтобы они были семантически отделены. К сожалению, если вы не будете осторожны, у вас может получиться странная путаница семантики, которую можно описать только как «семантически запутанную».