Другие отметили трудность с вашим интерфейсом, которая заключается в том, что нет никакого способа четко определить классы, которые могут работать взаимно с другими элементами своего класса; эта трудность в некоторой степени связана с тем фактом, что такие классы нарушают Принцип замещения Лискова; если класс принимает два объекта типа baseQ и ожидает, что они будут работать друг над другом, тогда LSP будет определять, что один из объектов baseQ должен иметь производныйQ. Это, в свою очередь, подразумевает, что baseQ должен работать на производномQ, а производныйQ должен работать на основе Q. В более широком смысле любая производная от baseQ должна работать на любой другой производной baseQ. Интерфейс, таким образом, не ковариантен, ни контравариантен, ни инвариантен, а скорее не является общим.
Если причина, по которой вы хотите использовать дженерики, заключается в том, чтобы позволить своим интерфейсам воздействовать на структуры без бокса, шаблон, заданный в ответе phoog, является хорошим. В общем случае не стоит беспокоиться о наложении рефлексивных ограничений на параметры типа, поскольку цель интерфейсов должна использоваться не как ограничения, а не как переменные или типы параметров, а необходимые условия могут быть наложены подпрограммой с использованием ограничений (например, 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>
, другому, который также является одним из тех типов (одинаковых или разных), не будет принимать моментальный снимок, а просто скопирует ссылку на более раннюю версию, созданный моментальный снимок.
Иногда бывает полезно иметь семантическую эквивалентность всех копий переменной. Есть и другие времена, когда может быть полезно, чтобы они были семантически отделены друг от друга. К сожалению, если вы не будете осторожны, вы можете столкнуться с нечетным mish-mosh семантики, который может быть описан только как «семантически запутанный».
Договор заключается в том, что он должен принять * любой * 'IVector', а не только вашу данную реализацию. C# не поддерживает дисперсию аргумента как способ переопределения методов или выполнения интерфейсов. –
Да, я только что осознал это сам, TY в любом случае. –