C# 为什么类类型参数的方差必须与其方法的方差相匹配';返回/参数类型参数?

C# 为什么类类型参数的方差必须与其方法的方差相匹配';返回/参数类型参数?,c#,generics,generic-variance,C#,Generics,Generic Variance,以下投诉: interface IInvariant<TInv> {} interface ICovariant<out TCov> { IInvariant<TCov> M(); // The covariant type parameter `TCov' // must be invariantly valid on // `ICovariant

以下投诉:

interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
    IInvariant<TCov> M(); // The covariant type parameter `TCov'
                          // must be invariantly valid on
                          // `ICovariant<TCov>.M()'
}
interface IContravariant<in TCon> {
    void M(IInvariant<TCon> v); // The contravariant type parameter
                                // `TCon' must be invariantly valid
                                // on `IContravariant<TCon>.M()'
}
接口IInvariant{}
接口变量{
IInvariant M();//协变类型参数'TCov'
//必须在上保持不变有效
//`ICovariant.M()'
}
接口IContravant{
void M(IInvariant v);//逆变类型参数
//'TCon'必须是不变有效的
//关于'IContravariant.M()'
}
但我无法想象这在哪里是不安全的。(snip*)这是不允许的原因,还是有其他违反类型安全的情况,我不知道


*诚然,我最初的想法令人费解,但尽管如此,回答还是非常透彻,甚至以令人印象深刻的准确性剖析了我最初的假设

除了retrospect的一记漂亮的耳光之外,我还意识到我错误地认为
ICovariant::M
的类型签名在其
ICovariant
被分配给
ICovariant
时仍然是
Func
。然后,将
M
分配给
Func
看起来很好,但这当然是非法的。为什么不干脆禁止这最后一个显然是非法的演员?(所以我想)

我觉得这个错误的、切题的猜测有损于这个问题,正如我也指出的,但出于历史目的,被剪掉的部分:

对我来说,最直观的解释是,以
ICovariant
为例,协变
TCov
意味着方法
IInvariant M()
可以转换为一些
IInvariant M()
,其中
TSuper-TCov
,这违反了
IInvariant
TInv
的不变性。然而,这一含义似乎并不必要:通过禁止施放
M
,可以很容易地实现
IInvariant
TInv
上的不变性


让我们看一个更具体的例子。我们将对这些接口进行几个实现:

class InvariantImpl<T> : IInvariant<T>
{
}

class CovariantImpl<T> : ICovariant<T>
{
    public IInvariant<T> M()
    {
        return new InvariantImpl<T>();
    }
}
因为
ICovariant
是协变的,这是一个有效的方法调用,我们可以在任何需要
ICovariant
的地方替换
ICovariant
,因为协变

但我们有个问题。在
Foo
内部,我们调用
ICovariant.M()
并期望它返回一个
IInvariant
,因为
ICovariant
接口说它会这样做。但它不能这样做,因为我们所传递的实际实现实际上实现了
ICovariant
,它的
M
方法返回
IInvariant
,由于接口的不变性,它与
IInvariant
无关。它们是完全不同的类型

为什么类类型参数的方差必须与其方法的返回/参数类型参数的方差相匹配

没有

返回类型和参数类型不需要匹配封闭类型的差异。在您的示例中,对于两种封闭类型,它们都需要是协变的。这听起来有悖常理,但其原因将在下面的解释中变得显而易见


为什么您提出的解决方案无效 协变的
TCov
意味着方法
IInvariant M()
可以被强制转换为一些
IInvariant M()
,其中
TSuper-TCov
,这违反了
IInvariant
TInv
的不变性。然而,这一含义似乎并不必要:通过禁止施放
M
,可以很容易地实现
IInvariant
TInv
上的不变性

  • 您要说的是,可以将具有variant类型参数的泛型类型分配给具有相同泛型类型定义和不同类型参数的另一个类型。那部分是正确的
  • 但是您也在说,为了解决潜在的子类型冲突问题,方法的明显特征不应该在过程中改变。那是不对的
例如,
ICovariant
有一个方法
IInvariant M()
。“不允许强制转换
M
”意味着当
ICovariant
被分配给
ICovariant
时,它仍然保留签名为
IInvariant M()
的方法。如果这是允许的,那么这个完全有效的方法将有一个问题:

void Test(ICovariant<object> arg)
{
    var obj = arg.M();
}
显然,方法的静态已知返回类型(
M()
)不可能依赖于对象的运行时类型实现的接口(
ICovariant

因此,当泛型类型被分配给另一个具有更一般类型参数的泛型类型时,使用相应类型参数的成员签名也必须更改为更一般的类型。如果我们想保持类型安全,就没有办法了。现在让我们看看“更一般”在每种情况下的含义


为什么
ICovariant
要求
IInvariant
是协变的 对于
string
的类型参数,编译器“看到”此具体类型:

interface ICovariant<string>
{
    IInvariant<string> M();
}
interface IContravariant<object>
{
    void M(IInvariant<object> v); 
}
interface IContravariant<string>
{
    void M(IInvariant<string> v); 
}
假设实现前一个接口的类型:

class MyType : ICovariant<string>
{
    public IInvariant<string> M() 
    { /* ... */ }
}
class MyType : IContravariant<object>
{
    public void M(IInvariant<object> v)
    { /* ... */ }
}
class TigerCageFactory : ICageFactory<Tiger> 
{ 
  public ICage<Tiger> MakeCage() { return new Cage<Tiger>(); }
}
…您还声称您现在可以这样做:

IInvariant<string> r1 = original.M();
IInvariant<object> r2 = covariant.M();
IInvariant<object> arg = Something();
original.M(arg);
IInvariant<string> arg2 = SomethingElse();
contravariant.M(arg2);
对于
string
的类型参数,编译器“看到”这个具体类型:

interface ICovariant<string>
{
    IInvariant<string> M();
}
interface IContravariant<object>
{
    void M(IInvariant<object> v); 
}
interface IContravariant<string>
{
    void M(IInvariant<string> v); 
}
…您还声称您现在可以这样做:

IInvariant<string> r1 = original.M();
IInvariant<object> r2 = covariant.M();
IInvariant<object> arg = Something();
original.M(arg);
IInvariant<string> arg2 = SomethingElse();
contravariant.M(arg2);
IInvariant arg=Something();
原件.M(arg);
IInvariant arg2=SomethingElse();
逆变型M(arg2);
同样,
original.M(arg)
contravariant.M(arg2)
是对同一方法的调用。该方法的实际实现要求我们传递任何
IInvariant
的内容

所以,在执行l
interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
   IInvariant<TCov> M(); // Error
}
interface ICage<TAnimal> {
  TAnimal Remove();
  void Insert(TAnimal contents);
}
interface ICageFactory<out T> {
   ICage<T> MakeCage();
}
class TigerCageFactory : ICageFactory<Tiger> 
{ 
  public ICage<Tiger> MakeCage() { return new Cage<Tiger>(); }
}
ICageFactory<Animal> animalCageFactory = new TigerCageFactory();
ICage<Animal> animalCage = animalCageFactory.MakeCage();
animalCage.Insert(new Fish());
interface ICageFiller<in T> {
   void Fill(ICage<T> cage);
}

class AnimalCageFiller : ICageFiller<Animal> {
  public void Fill(ICage<Animal> cage)
  {
    cage.Insert(new Fish());
  }
}
ICageFiller<Tiger> tigerCageFiller = new AnimalCageFiller();
tigerCageFiller.Fill(new Cage<Tiger>());
interface ICageFactory<out T> {
   ICage<T> MakeCage();
}