为了账号安全,请及时绑定邮箱和手机立即绑定

仍然对协方差和矛盾和输入/输出感到困惑

仍然对协方差和矛盾和输入/输出感到困惑

C#
慕村225694 2019-10-08 10:02:47
好的,我在stackoverflow上阅读了有关该主题的内容,观看了&this,但是对于co / contra-variance还是有些困惑。从这里协方差允许在原始类型仅用于“输出”位置(例如,作为返回值)的API中替换“较大”(较不具体)的类型。协变性允许在原始类型仅用于“输入”位置的API中替换“较小”(更具体)的类型。我知道这与类型安全有关。关于这in/out件事。我可以说我in何时需要写它,out何时需要它只读。和in装置,禁忌方差,out协方。但是根据上面的解释...与此例如,List<Banana>不能将a视为a,List<Fruit>因为  list.Add(new Apple())它对List有效,但对无效List<Banana>。所以不应该,如果我要使用in/要写入该对象,则它必须更大,更通用。我知道有人问过这个问题,但仍然很困惑。
查看完整描述

3 回答

?
aluckdog

TA贡献1847条经验 获得超7个赞

C#4.0中的协方差和协方差都涉及使用派生类而不是基类的能力。in / out关键字是编译器提示,用于指示是否将类型参数用于输入和输出。


协方差

C#4.0中的协方差由out关键字辅助,这意味着使用outtype参数的派生类的泛型类型是可以的。因此


IEnumerable<Fruit> fruit = new List<Apple>();

由于Apple是Fruit,List<Apple>可以安全地用作IEnumerable<Fruit>


逆差

矛盾是in关键字,它表示输入类型,通常在委托中。原理是相同的,这意味着委托可以接受更多派生类。


public delegate void Func<in T>(T param);

这意味着如果我们有一个Func<Fruit>,可以将其转换为Func<Apple>。


Func<Fruit> fruitFunc = (fruit)=>{};

Func<Apple> appleFunc = fruitFunc;

如果它们基本上是相同的东西,为什么将它们称为协/逆方差?

因为即使原理相同,也可以安全地从派生类型转换为基类,但在输入类型上使用时,我们可以安全地将较少派生类型(Func<Fruit>)强制转换为更多派生类型(Func<Apple>),这很有意义,因为任何需要Fruit,也可以服用Apple。


查看完整回答
反对 回复 2019-10-08
?
翻过高山走不出你

TA贡献1875条经验 获得超3个赞

让我分享我对这个话题的看法。


免责声明:忽略空分配,我使用它们来使代码保持相对简短,并且它们足以查看编译器想要告诉我们什么。

让我们从类的层次结构开始:

class Animal { }


class Mammal : Animal { }


class Dog : Mammal { }

现在定义一些接口,来说明什么in和out通用修饰符真正做到:

interface IInvariant<T>

{

    T Get(); // ok, an invariant type can be both put into and returned

    void Set(T t); // ok, an invariant type can be both put into and returned

}


interface IContravariant<in T>

{

    //T Get(); // compilation error, cannot return a contravariant type

    void Set(T t); // ok, a contravariant type can only be **put into** our class (hence "in")

}


interface ICovariant<out T>

{

    T Get(); // ok, a covariant type can only be **returned** from our class (hence "out")

    //void Set(T t); // compilation error, cannot put a covariant type into our class

}

好的,如果对接口in和out修饰符有限制,为什么还要麻烦使用它们呢?让我们来看看:


不变性

让我们从不变性开始(没有in,没有out修饰符)


不变性实验

考虑 IInvariant<Mammal>


IInvariant<Mammal>.Get() -返回哺乳动物

IInvariant<Mammal>.Set(Mammal) -接受哺乳动物

如果我们尝试:IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null?


呼叫者都IInvariant<Mammal>.Get()希望有哺乳动物,但是IInvariant<Animal>.Get()-会返回动物。并不是每个动物都是哺乳动物,所以它是不兼容的。

不管谁打电话,都IInvariant<Mammal>.Set(Mammal)希望哺乳动物能够通过。由于IInvariant<Animal>.Set(Animal)可以接受任何动物(包括哺乳动物),因此兼容

结论:这种分配是不相容的

而如果我们尝试:IInvariant<Mammal> invariantMammal = (IInvariant<Dog>)null?


呼叫者IInvariant<Mammal>.Get()期望有哺乳动物,IInvariant<Dog>.Get()-会返回一只狗,每只狗都是一只哺乳动物,因此是兼容的。

不管谁打电话,都IInvariant<Mammal>.Set(Mammal)希望哺乳动物能够通过。由于IInvariant<Dog>.Set(Dog)接受只狗(而不是每个哺乳动物如狗),这是不兼容的。

结论:这种分配是不相容的

让我们检查是否正确


IInvariant<Animal> invariantAnimal1 = (IInvariant<Animal>)null; // ok

IInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation error

IInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation error


IInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation error

IInvariant<Mammal> invariantMammal2 = (IInvariant<Mammal>)null; // ok

IInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation error


IInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation error

IInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation error

IInvariant<Dog> invariantDog3 = (IInvariant<Dog>)null; // ok

这一点很重要:值得注意的是,根据泛型类型参数在类层次结构中是较高还是较低,泛型类型本身由于不同的原因而不兼容。


好的,让我们找出如何利用它。


协方差(out)

使用out通用修饰符时,您将具有协方差(请参见上文)


如果我们的类型看起来像:ICovariant<Mammal>,则声明两件事:


我的一些方法返回了哺乳动物(因此是out通用修饰符)-这很无聊

我的方法都不接受哺乳动物-尽管这很有趣,因为这是通用修饰符施加的实际限制out

我们如何从out修饰符限制中受益?回顾上面的“不变性实验”的结果。现在尝试看看对协方差进行相同的实验会发生什么?


协方差实验

如果我们尝试:ICovariant<Mammal> covariantMammal = (ICovariant<Animal>)null?


呼叫者都ICovariant<Mammal>.Get()希望有哺乳动物,但是ICovariant<Animal>.Get()-会返回动物。并不是每个动物都是哺乳动物,所以它是不兼容的。

ICovariant.Set(Mammal) -由于out修改器的限制,这不再是一个问题!

结论这样的分配是不相容的

而如果我们尝试:ICovariant<Mammal> covariantMammal = (ICovariant<Dog>)null?


呼叫者ICovariant<Mammal>.Get()期望有哺乳动物,ICovariant<Dog>.Get()-会返回一只狗,每只狗都是一只哺乳动物,因此是兼容的。

ICovariant.Set(Mammal) -由于out修改器的限制,这不再是一个问题!

结论这样的分配是兼容的

让我们用代码进行确认:


ICovariant<Animal> covariantAnimal1 = (ICovariant<Animal>)null; // ok

ICovariant<Animal> covariantAnimal2 = (ICovariant<Mammal>)null; // ok!!!

ICovariant<Animal> covariantAnimal3 = (ICovariant<Dog>)null; // ok!!!


ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation error

ICovariant<Mammal> covariantMammal2 = (ICovariant<Mammal>)null; // ok

ICovariant<Mammal> covariantMammal3 = (ICovariant<Dog>)null; // ok!!!


ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation error

ICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation error

ICovariant<Dog> covariantDog3 = (ICovariant<Dog>)null; // ok

协方差(in)

使用in泛型修饰符时,您会产生矛盾(请参见上文)


如果我们的类型看起来像:IContravariant<Mammal>,则声明两件事:


我的一些方法接受哺乳动物(因此in通用修饰符)-这很无聊

我的方法都没有返回哺乳动物-尽管这很有趣,因为这是通用修饰符施加的实际限制in

协方差实验

如果我们尝试:IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null?


IContravariant<Mammal>.Get()-由于in修改器的限制,这不再是问题!

不管谁打电话,都IContravariant<Mammal>.Set(Mammal)希望哺乳动物能够通过。由于IContravariant<Animal>.Set(Animal)可以接受任何动物(包括哺乳动物),因此兼容

结论:这样的分配是兼容的

而如果我们尝试:IContravariant<Mammal> contravariantMammal = (IContravariant<Dog>)null?


IContravariant<Mammal>.Get()-由于in修改器的限制,这不再是问题!

不管谁打电话,都IContravariant<Mammal>.Set(Mammal)希望哺乳动物能够通过。由于IContravariant<Dog>.Set(Dog)接受只狗(而不是每个哺乳动物如狗),这是不兼容的。

结论:这种分配是不相容的

让我们用代码进行确认:


IContravariant<Animal> contravariantAnimal1 = (IContravariant<Animal>)null; // ok

IContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation error

IContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation error


IContravariant<Mammal> contravariantMammal1 = (IContravariant<Animal>)null; // ok!!!

IContravariant<Mammal> contravariantMammal2 = (IContravariant<Mammal>)null; // ok

IContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation error


IContravariant<Dog> contravariantDog1 = (IContravariant<Animal>)null; // ok!!!

IContravariant<Dog> contravariantDog2 = (IContravariant<Mammal>)null; // ok!!!

IContravariant<Dog> contravariantDog3 = (IContravariant<Dog>)null; // ok

顺便说一句,这有点违反直觉,不是吗?


// obvious

Animal animal = (Dog)null; // ok

Dog dog = (Animal)null; // compilation error, not every Animal is a Dog


// but this looks like the other way around

IContravariant<Animal> contravariantAnimal = (IContravariant<Dog>) null; // compilation error

IContravariant<Dog> contravariantDog = (IContravariant<Animal>) null; // ok

为什么不兼得?

因此,我们可以同时使用in和out一般的修饰?-显然不是。


为什么?回顾一下限制in和out修饰符施加的限制。如果我们想使我们的泛型类型参数既协变又是协变的,我们基本上可以说:


接口的所有方法均未返回 T

我们接口的方法均不接受 T

这实质上会使我们的通用接口成为非通用接口。


如何记住它?

你可以用我的把戏:)


“协变量”比“ contravaraint”短,这与它们的修饰符的长度相反(分别为“ out”和“ in”)

相反 varaint有点反直观(请参见上面的示例)


查看完整回答
反对 回复 2019-10-08
  • 3 回答
  • 0 关注
  • 337 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信