C# 在反变位置使用协变类型会导致什么冲突?

C# 在反变位置使用协变类型会导致什么冲突?,c#,covariance,C#,Covariance,考虑一个具有协变类型T的接口。我正在研究这样一种情况:使用T的接口的所有派生类中的属性都是只读的,如果是泛型类,则是协变的。假设这个接口定义了一个使用T作为参数类型的方法。它允许哪些违规行为 例如,考虑: interface ICov<out T> { void maybe_safe_set(T v); } class ImplCov<T> : ICov<T> { public readonly T a; public readonly IEnum

考虑一个具有协变类型
T
的接口。我正在研究这样一种情况:使用
T
的接口的所有派生类中的属性都是只读的,如果是泛型类,则是协变的。假设这个接口定义了一个使用
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
  }
}
class Impl : ICov<Dog>
{
  public void maybe_safe_set(Dog v)
  {
    v.Woof(); // our 'Dog' v can really bark
  }
}
接口ICov{ 无效可能安全设置(tV); } 类ImplCov:ICov{ 公共只读T a; 公共只读IEnumerable b; 公共只读IEnumerable c; //public readonly IList d;//但不是这个 公共真空安全装置(电视){ //做一些不能改变状态的事情:我们的 //只读,无法修改协变IEnumerable成员 } } 在C#中,我得到了错误:

无效差异:类型参数“T”在“ConsoleApplication.ICov.maybe_safe_set(T)”上必须相反有效T’是协变的


这并不奇怪,因为
T
处于逆变位置。然而,我想不出这里会发生什么违规行为

我不知道其他人的情况,但我觉得这些东西也让人困惑。为了理解它,我需要创建一个示例来查看编译器正在防止哪些冲突

让我们先看看它,界面中没有方法。这些都是有效的:

interface ICov<out T> {}

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

ICov<BaseClass> x = new MyCov<InheritedClass>();
现在,我们可以确切地看到这将产生的问题:

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());
var usesInheritedClass=new MyCov();
//这是合法的,因为ICov中的类型参数是协变的
ICov USESBASESCLASS=使用其他ITEDCLASS;
//这就是它变坏的地方。
usesBaseClass.ListOfT.Add(新的基类());
usesInheritedClass
包含继承类的列表。但由于我可以将其转换为
ICov
,现在我可以将
基类添加到
继承类的列表中,但该列表无法工作

因此,尽管编译器错误令人困惑,但它位于正确的位置。它不会试图整理和修复我的下游错误。它通过防止这些类型可能混淆的情况,完全防止了这些下游错误。

您:

interface ICov<out T>    // BAD!
{
  void maybe_safe_set(T v);
}
然后考虑:

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
  }
}
class Impl : ICov<Dog>
{
  public void maybe_safe_set(Dog v)
  {
    v.Woof(); // our 'Dog' v can really bark
  }
}
类Impl:ICov { 公共空间安全设置(狗v) { v、 Woof();//我们的“狗”真会叫 } }
这将很好地编译

那么这个,

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!
var Impl=new Impl();
ICov impl2=impl1;//好的,实现它
ICov impl3=impl2;//好的,您声明接口是协变的('out')!
var badamal=新猫();
可能是安全的(坏动物);//ICov接收动物,对吗?
//哦,天哪,你疯了,一只“猫”叫!

当人们询问协变和逆变时,总是同一个例子。

协变意味着该类型仅从接口中使用,从未传递给它。这样想吧,
out
关键字意味着类型应该只出现在接口之外,但是您将它作为
maybe\u safe\u set
方法的参数输入<代码>T
可以定义为反向变量。接口是一个契约,它不知道给定的实现可能做什么。
public static类C{public static t Value}
public void maybe_safe_set(tv){C.Value=v}
((ICov)new ImplCov())。maybe_safe_set(new object())
。现在,您尝试将
对象
分配给
string
类型的
C.Value
。如果允许,您可以将
ImpleConv
强制转换为
ICov
,然后您可以调用
maybe\u safe\u set
,pass不是
string
,然后您将出现运行时类型错误。所以它实际上与实现类的实现细节没有任何关系。当您尝试它时发生了什么?试着回答你的问题不是很简单吗?这里的注释解释了为什么将
T
的值传递到
maybe\u safe\u set()
方法对于
out T
类型参数是非法的。协变/反变的全部要点是,当您误用泛型类型时,编译器将发出错误。所以,如果你有一个错误,你就是在误用它。如果“举手”的意思是“生成了一个编译器错误”,你应该这样说,提供错误的确切文本,并解释你对错误不了解的地方。@juharr这是一个很好的观点。如果没有静态类,我也无法想到一个可以作用于特定类型的实现,但PetSerAl的注释也是一个完美的例子。实际上,我将
只读
IEnumerable
引入到这个问题中,以避免这种完全违反!我认为状态是问题所在,所以我试图使它尽可能不可变,但是类型化方法,正如Jeppe指出的,仍然存在问题。我认为我有一个很好的例子。但你不能打败让猫吠叫。这一条将永远被归档,以备将来剽窃之用。“当人们询问协变项和逆变项时,总是同一个例子”——是的,总是这样。这意味着一个问题:为什么要再次回答这个问题,而不是将它标记为具有相同示例的某个问题的副本?