2012-01-06 10 views
0
public interface IVector<TScalar> { 
    void Add(ref IVector<TScalar> addend); 
} 

public struct Vector3f : IVector<float> { 
    public void Add(ref Vector3f addend); 
} 

Compiler Ответ:Нарушение контракта на замену структуры/класса на интерфейс?

"Vector3f не реализует интерфейс члена IVector<float>.Add(ref IVector<float>)"

+2

Договор заключается в том, что он должен принять * любой * 'IVector ', а не только вашу данную реализацию. C# не поддерживает дисперсию аргумента как способ переопределения методов или выполнения интерфейсов. –

+0

Да, я только что осознал это сам, TY в любом случае. –

ответ

2

Но вы можете сделать это:

public interface IVector<T, TScalar> 
    where T : IVector<T, TScalar> 
{ 
    void Add(ref T addend); 
} 

public struct Vector3f : IVector<Vector3f, float> 
{ 
    public void Add(ref Vector3f addend) 
    { 
    } 
} 

Однако, это означает, что у вас есть изменяемые структуры , которого вы не должны. Для того, чтобы иметь неизменные них, вам нужно переопределить интерфейс:

public interface IVector<T, TScalar> 
    where T : IVector<T, TScalar> 
{ 
    T Add(T addend); 
} 

public struct Vector3f : IVector<Vector3f, float> 
{ 
    public Vector3f Add(Vector3f addend) 
    { 
    } 
} 

EDIT:

Как Энтони Пеграм указывает, есть отверстия в этой модели. Тем не менее, он широко используется. Например:

struct Int32 : IComparable<Int32> ... 

Для получения дополнительной информации, вот ссылка на статью Эрика Липперта Curiouser and curiouser об этой модели.

+0

Однако, учитывая это, вы также можете определить 'EvilVector3f: IVector ', потому что до сих пор не существует способа четкого выражения идеи о том, что интерфейс или абстрактный метод можно реализовать таким образом, чтобы он работал только с типом класса, который его реализует. (*, что я знаю, и я признаю, что я идиот.) –

+0

Согласитесь, очевидно, это неправильный подход и не то, что я ищу, поскольку он не отделяет интерфейс от реализации. –

+0

@ AnthonyPegram хороший пункт. Это называется «любопытно повторяющимся шаблоном шаблонов» на C++ и, возможно, его можно назвать «любопытно повторяющимся общим шаблоном» на C#, но Google дает только 1390 просмотров для этой фразы. Я добавил ссылку на статью Эрика Липперта по этому вопросу. – phoog

1

Другие отметили трудность с вашим интерфейсом, которая заключается в том, что нет никакого способа четко определить классы, которые могут работать взаимно с другими элементами своего класса; эта трудность в некоторой степени связана с тем фактом, что такие классы нарушают Принцип замещения Лискова; если класс принимает два объекта типа 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 семантики, который может быть описан только как «семантически запутанный».

+0

+1 для подробного обсуждения – phoog