Контракт замены структуры / класса на интерфейс нарушен? - PullRequest
0 голосов
/ 06 января 2012
public interface IVector<TScalar> {
    void Add(ref IVector<TScalar> addend);
}

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

Ответ компилятора:

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

Ответы [ 2 ]

2 голосов
/ 06 января 2012

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

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)
    {
    }
} 

РЕДАКТИРОВАТЬ:

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

struct Int32 : IComparable<Int32> ...

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

1 голос
/ 06 января 2012

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

Иногда бывает полезно, чтобы все копии переменной были семантически эквивалентными. В других случаях может быть полезно, чтобы они были семантически отделены. К сожалению, если вы не будете осторожны, у вас может получиться странная путаница семантики, которую можно описать только как «семантически запутанную».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...