3 回答
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。
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有点反直观(请参见上面的示例)
- 3 回答
- 0 关注
- 345 浏览
添加回答
举报