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

好的,我在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/要写入该对象,则它必须更大,更通用。


我知道有人问过这个问题,但仍然很困惑。


慕村225694
浏览 338回答 3
3回答

aluckdog

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。

翻过高山走不出你

让我分享我对这个话题的看法。免责声明:忽略空分配,我使用它们来使代码保持相对简短,并且它们足以查看编译器想要告诉我们什么。让我们从类的层次结构开始:class Animal { }class Mammal : Animal { }class Dog : Mammal { }现在定义一些接口,来说明什么in和out通用修饰符真正做到:interface IInvariant<T>{&nbsp; &nbsp; T Get(); // ok, an invariant type can be both put into and returned&nbsp; &nbsp; void Set(T t); // ok, an invariant type can be both put into and returned}interface IContravariant<in T>{&nbsp; &nbsp; //T Get(); // compilation error, cannot return a contravariant type&nbsp; &nbsp; void Set(T t); // ok, a contravariant type can only be **put into** our class (hence "in")}interface ICovariant<out T>{&nbsp; &nbsp; T Get(); // ok, a covariant type can only be **returned** from our class (hence "out")&nbsp; &nbsp; //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; // okIInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation errorIInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation errorIInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation errorIInvariant<Mammal> invariantMammal2 = (IInvariant<Mammal>)null; // okIInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation errorIInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation errorIInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation errorIInvariant<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; // okICovariant<Animal> covariantAnimal2 = (ICovariant<Mammal>)null; // ok!!!ICovariant<Animal> covariantAnimal3 = (ICovariant<Dog>)null; // ok!!!ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation errorICovariant<Mammal> covariantMammal2 = (ICovariant<Mammal>)null; // okICovariant<Mammal> covariantMammal3 = (ICovariant<Dog>)null; // ok!!!ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation errorICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation errorICovariant<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; // okIContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation errorIContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation errorIContravariant<Mammal> contravariantMammal1 = (IContravariant<Animal>)null; // ok!!!IContravariant<Mammal> contravariantMammal2 = (IContravariant<Mammal>)null; // okIContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation errorIContravariant<Dog> contravariantDog1 = (IContravariant<Animal>)null; // ok!!!IContravariant<Dog> contravariantDog2 = (IContravariant<Mammal>)null; // ok!!!IContravariant<Dog> contravariantDog3 = (IContravariant<Dog>)null; // ok顺便说一句,这有点违反直觉,不是吗?// obviousAnimal animal = (Dog)null; // okDog dog = (Animal)null; // compilation error, not every Animal is a Dog// but this looks like the other way aroundIContravariant<Animal> contravariantAnimal = (IContravariant<Dog>) null; // compilation errorIContravariant<Dog> contravariantDog = (IContravariant<Animal>) null; // ok为什么不兼得?因此,我们可以同时使用in和out一般的修饰?-显然不是。为什么?回顾一下限制in和out修饰符施加的限制。如果我们想使我们的泛型类型参数既协变又是协变的,我们基本上可以说:接口的所有方法均未返回 T我们接口的方法均不接受 T这实质上会使我们的通用接口成为非通用接口。如何记住它?你可以用我的把戏:)“协变量”比“ contravaraint”短,这与它们的修饰符的长度相反(分别为“ out”和“ in”)相反 varaint有点反直观(请参见上面的示例)
打开App,查看更多内容
随时随地看视频慕课网APP