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

con*_*cat 10 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<TCov>.M()'
}
interface IContravariant<in TCon> {
    void M(IInvariant<TCon> v); // The contravariant type parameter
                                // `TCon' must be invariantly valid
                                // on `IContravariant<TCon>.M()'
}
Run Code Online (Sandbox Code Playgroud)

但我无法想象这不会是类型安全的.(剪断*)这是不允许这样做的原因,还是有其他违反类型安全的情况我不知道?


*我最初的想法确实令人费解,但尽管如此,答案非常彻底,@ Theodoros Chatzigiannakis甚至以令人印象深刻的准确性解剖了我的初步假设.

除了回顾过去的好评之外,我意识到我错误地认为类型签名ICovariant::M仍然是一个Func<IInvariant<Derived>>当它ICovariant<Derived>被分配给a时ICovariant<Base>.然后,该分配MFunc<IInvariant<Base>>看起来罚款来自未来ICovariant<Base>,但当然会是非法的.为什么不禁止最后这个显然是非法的演员?(所以我认为)

正如埃里克·利珀特(Eric Lippert)所指出的那样,我觉得这种错误和切向猜测会减少这个问题,但出于历史目的,这个被剪切的部分:

对我来说最直观的解释是,以ICovariant协变为例,TCov意味着该方法IInvariant<TCov> M()可以转换到某些IInvariant<TSuper> M()地方TSuper super TCov,这违反了TInvin 的不变性IInvariant.然而,这种暗示似乎并不是必要的:IInvarianton 的不变性TInv很容易通过禁止演员来强制执行M.

Kyl*_*yle 6

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

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

class CovariantImpl<T> : ICovariant<T>
{
    public IInvariant<T> M()
    {
        return new InvariantImpl<T>();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们假设编译器没有抱怨这个并尝试以一种简单的方式使用它:

static IInvariant<object> Foo( ICovariant<object> o )
{
    return o.M();
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好. oICovariant<object>和那个接口保证我们有一个可以返回的方法IInvariant<object>.我们不必在这里进行任何演员表或转换,一切都很好.现在让我们调用方法:

var x = Foo( new CovariantImpl<string>() );
Run Code Online (Sandbox Code Playgroud)

因为ICovariant是协变的,所以这是一个有效的方法调用,我们可以替换因为协方差而需要的ICovariant<string>东西ICovariant<object>.

但是我们遇到了问题.在内部Foo,我们调用ICovariant<object>.M()并期望它返回一个,IInvariant<object>因为这就是ICovariant界面所说的.但它不能这样做,因为我们传递的实际实现实际上实现了ICovariant<string>它的M方法返回IInvariant<string>,这与该接口的不变性无关IInvariant<object>.它们是完全不同的类型.


Eri*_*ert 5

到目前为止,我不确定你的答案是否真的得到了答案.

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

它没有,所以问题是基于错误的前提.实际规则如下:

https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

现在考虑:

interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
   IInvariant<TCov> M(); // Error
}
Run Code Online (Sandbox Code Playgroud)

这是不允许这样做的原因,还是有其他违反类型安全的情况我不知道?

我没有按照你的解释,所以我们只是说明为什么在不参考你的解释的情况下不允许这样做.在这里,让我用一些等价的类型替换这些类型. IInvariant<TInv>可以是T中不变的任何类型,让我们说ICage<TCage>:

interface ICage<TAnimal> {
  TAnimal Remove();
  void Insert(TAnimal contents);
}
Run Code Online (Sandbox Code Playgroud)

也许我们有一种Cage<TAnimal>实现的类型ICage<TAnimal>.

让我们更换ICovariant<T>

interface ICageFactory<out T> {
   ICage<T> MakeCage();
}
Run Code Online (Sandbox Code Playgroud)

让我们实现界面:

class TigerCageFactory : ICageFactory<Tiger> 
{ 
  public ICage<Tiger> MakeCage() { return new Cage<Tiger>(); }
}
Run Code Online (Sandbox Code Playgroud)

一切都进展顺利.ICageFactory是协变的,所以这是合法的:

ICageFactory<Animal> animalCageFactory = new TigerCageFactory();
ICage<Animal> animalCage = animalCageFactory.MakeCage();
animalCage.Insert(new Fish());
Run Code Online (Sandbox Code Playgroud)

我们只是把鱼放进虎笼里.那里的每一步都完全合法,我们最终违反了类型系统.我们得出的结论是,首先要做出ICageFactory协变一定不是合法的.

让我们来看看你的逆变例子; 它基本相同:

interface ICageFiller<in T> {
   void Fill(ICage<T> cage);
}

class AnimalCageFiller : ICageFiller<Animal> {
  public void Fill(ICage<Animal> cage)
  {
    cage.Insert(new Fish());
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,界面是逆变的,所以这是合法的:

ICageFiller<Tiger> tigerCageFiller = new AnimalCageFiller();
tigerCageFiller.Fill(new Cage<Tiger>());
Run Code Online (Sandbox Code Playgroud)

我们又一次将鱼放入虎笼中.我们再次得出结论,首先制造类型逆变一定是非法的.

现在让我们考虑一下我们如何知道这些都是非法的问题.在第一种情况下我们有

interface ICageFactory<out T> {
   ICage<T> MakeCage();
}
Run Code Online (Sandbox Code Playgroud)

相关规则是:

所有非void接口方法的返回类型必须是covariantly有效.

ICage<T>"有效的共生"吗?

如果类型是:1)指针类型或非泛型类,则该类型是有效的... NOPE 2)数组类型... NOPE 3)泛型类型参数类型... NOPE 4)构造类,struct,enum,interface或delegate type X<T1, … Tk>YES!...如果第i个类型参数被声明为不变量,则Ti必须是不变的有效.

TAnimal是不变的ICage<TAnimal>,所以TICage<T>必须有效目不暇接.是吗?否.要有效目不暇接,它必须是有效的两个协变和contravariantly,但它是有效的只有协变.

因此这是一个错误.

对逆变情况进行分析是一项练习.