2016-07-20 2 views
0

Рассмотрим интерфейс с ковариантным типом T. Я рассматриваю случай, когда свойства во всех производных классах этого интерфейса, которые используют T, являются readonly и ковариантными, если общий класс. Предположим, что этот интерфейс затем определяет метод, который использует T в качестве типа аргумента. Какие нарушения допускаются?Какие нарушения допускают использование ковариантного типа в контравариантной позиции?

Для примера рассмотрим:

interface ICov<out T> { 
    void maybe_safe_set(T v); 
} 
class ImplCov<T> : ICov<T> { 
    public readonly T a; 
    public readonly IEnumerable<T> b; 
    public readonly IEnumerable<IEnumerable<T>> c; 
    // public readonly IList<T> d; // but not this 

    public void maybe_safe_set(T v) { 
    // do things that can't modify state: the type of our 
    // readonly, covariant IEnumerable members can't be modified 
    } 
} 

В C#, я получаю ошибку:

Invalid variance: The type parameter 'T' must be contravariantly valid on 'ConsoleApplication.ICov.maybe_safe_set(T)'. 'T' is covariant.

что не удивительно, так как T находится в контравариантного положении. Однако я не могу думать о нарушении, которое может произойти здесь.

+0

Ковариация означает, что тип потребляется только из интерфейса, никогда не передавался ему. Подумайте об этом таким образом, ключевое слово 'out' означает, что тип должен выйти только из интерфейса, но вы используете его как аргумент метода' maybe_safe_set'. Вместо «T» вместо этого определяется как контравариантная. Интерфейс - это контракт, и он не знает, что может сделать данная реализация. – juharr

+2

'public static class C {public static T Value}' и 'public void maybe_safe_set (T v) {C .Value = v}' и '((ICov ) новый ImplCov ()). Maybe_safe_set (новый объект()) '. Теперь вы пытаетесь присвоить 'object'' C .Value' типа 'string'. – PetSerAl

+1

Если это было разрешено, вы могли бы наложить 'ImpleConv ' на 'ICov ', а затем вы могли бы вызвать' maybe_safe_set', и передать это то, что не является 'string', и тогда у вас будет ошибка типа времени выполнения. Так что, действительно, он не имеет ничего общего с деталями реализации класса реализации. – juharr

ответ

1

Вы:

interface ICov<out T> // BAD! 
{ 
    void maybe_safe_set(T v); 
} 

Здесь возникает проблема. Как обычно, мы имеем:

class Animal { /* ... */ } 
class Dog : Animal { public void Woof() { } /* ... */ } 
class Cat : Animal { /* ... */ } 

Тогда рассмотрим:

class Impl : ICov<Dog> 
{ 
    public void maybe_safe_set(Dog v) 
    { 
    v.Woof(); // our 'Dog' v can really bark 
    } 
} 

, который компилируется просто отлично.

Тогда это:

var impl1 = new Impl(); 
ICov<Dog> impl2 = impl1; // OK, implements that 
ICov<Animal> impl3 = impl2; // OK, you claim interface is covariant ('out')! 

var badAnimal = new Cat(); 
impl3.maybe_safe_set(badAnimal); // ICov<Animal> takes in Animal, right? 

// oh my God, you mad a 'Cat' bark! 

Это всегда тот же самый пример, когда люди спрашивают о со- и контрвариации.

+0

Я думал, что у меня был хороший пример. Но вы не можете победить кошку. Этот навсегда подан для будущего плагиата. –

+0

_ «Это всегда тот же пример, когда люди спрашивают о совместной и контравариантности» - да, это всегда так. Из чего вытекает вопрос: зачем отвечать на вопрос _again_, а не отмечать его как дубликат одного из тех вопросов, который имеет тот же пример? –

0

Я не знаю обо всех остальных, но я считаю, что это тоже запутывает. Чтобы понять это, мне нужно создать пример, чтобы увидеть, какие нарушения предотвращает компилятор.

Давайте рассмотрим его сначала без метода в интерфейсе. Это все действительно:

interface ICov<out T> {} 

public class BaseClass { } 
public class InheritedClass: BaseClass { } 

ICov<BaseClass> x = new MyCov<InheritedClass>(); 

Поскольку T ковариантен, переменная типа ICov<BaseClass> может относиться к экземпляру MyCov<T> где T происходит от BaseClass.

А что, если мы могли бы добавить метод, который работает от типа T к интерфейсу (что компилятор мешает?)

interface ICov<out T> 
{ 
    List<T> ListOfT { get; set; } 
    // ^^^ compiler doesn't like this. 
} 

public class BaseClass { } 
public class InheritedClass: BaseClass { } 

public class MyCov<T> : ICov<T> { 
    public List<T> ListOfT { get; set; } = new List<T>(); 
} 

Теперь мы можем увидеть именно проблему это создало бы:

var usesInheritedClass = new MyCov<InheritedClass>(); 

//This is legal, because the type parameter in ICov is covariant 
ICov<BaseClass> usesBaseClass = usesInheritedClass; 

//Here's where it goes bad. 
usesBaseClass.ListOfT.Add(new BaseClass()); 

usesInheritedClass содержит список InheritedClass. Но поскольку я могу использовать его как ICov<BaseClass>, теперь я могу добавить BaseClass в список InheritedClass, который не может работать.

Так что, хотя ошибка компилятора запутанна, она находится в правильном месте. Он не пытается сортировать и исправлять мои ошибки ниже по потоку. Он предотвращает все последующие ошибки, предотвращая сценарий, в котором эти типы могут запутаться.

+0

На самом деле, я привёл 'readonly' и' IEnumerable' в этот вопрос, чтобы избежать этого точного нарушения! Я думал, что проблема была в состоянии, поэтому я постарался сделать ее максимально неизменной, но типичные методы, как указывает Джепп, остаются проблематичными. – concat

Смежные вопросы