What violations does using a covariant type in a contravariant position here enable?

大憨熊 提交于 2019-12-25 08:48:13

问题


Consider an interface with a covariant type T. I'm examining the case where properties in all derived classes of this interface that use T are readonly, and covariant if a generic class. Suppose this interface then defines a method that uses T as an argument type. What violations does it permit?

For example, consider:

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

In C#, I get the error:

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

which is not surprising, since T is found in a contravariant position. However, I can't think of a violation that can occur here.


回答1:


You:

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

Here comes the problem. As usual we have:

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

Then consider:

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

which will compile just fine.

Then this:

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!

It is always the same example when people ask about co- and contravariance.




回答2:


I don't know about everyone else, but I find this stuff confusing too. In order to make sense of it, I need to create an example to see what violations the compiler is preventing.

Let's look at it first without the method in the interface. This is all valid:

interface ICov<out T> {}

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

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

Because T is covariant, a variable of type ICov<BaseClass> can refer to an instance of MyCov<T> where T derives from BaseClass.

Now what if we could add a method that operates on type T to the interface (what the compiler is preventing?)

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>();
}

Now we can see exactly the problem this would create:

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 contains a list of InheritedClass. But because I can cast it as ICov<BaseClass>, now I can add a BaseClass to a list of InheritedClass, which can't work.

So although the compiler error is confusing, it's in the right place. It doesn't try to sort out and fix my downstream errors. It prevents those downstream errors altogether by preventing a scenario in which those types can get confused.



来源:https://stackoverflow.com/questions/38486546/what-violations-does-using-a-covariant-type-in-a-contravariant-position-here-ena

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!