仍然对协变和逆变感到困惑输入/输出
好吧,我在 stackoverflow 上读了一些关于这个主题的内容,观看了 this & 此 ,但对协方差/反方差仍然有点困惑。
来自此处
协方差允许“更大”(更少 特定)类型被替换为 仅原始类型的 API 用于“输出”位置(例如 返回值)。逆变允许 “更小”(更具体)的类型 替换为 API,其中 原始类型仅用于 “输入”位置。
我知道这与类型安全有关。
关于in/out
的事情。我可以说当我需要写入时我使用in
,而当我需要写入时我使用out
。 in
表示逆变,out
表示协方差。但从上面的解释......
和这里
例如,
List
不能 被视为List
因为list.Add(new Apple())
适用于 列出但不适用于List
。
所以不应该是这样,如果我要使用 in
/ am 要写入对象,它必须更大更通用。
我知道这个问题已经被问过,但仍然很困惑。
ok i read a bit on this topic on stackoverflow, watched this & this, but still a bit confused about co/contra-variance.
from here
Covariance allows a "bigger" (less
specific) type to be substituted in an
API where the original type is only
used in an "output" position (e.g. as
a return value). Contravariance allows
a "smaller" (more specific) type to be
substituted in an API where the
original type is only used in an
"input" position.
i know it has to do with type safety.
about the in/out
thing. can i say i use in
when i need to write to it, and out
when its read only. and in
means contra-variance, out
co-variance. but from the explanation above...
and here
For example, a
List<Banana>
can't be
treated as aList<Fruit>
becauselist.Add(new Apple())
is valid for
List but not forList<Banana>
.
so shouldn't it be, if i were to use in
/ am going to write to the object, it must be bigger more generic.
i know this question has been asked but still very confused.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
协方差很容易理解。这是很自然的。逆变更令人困惑。
仔细查看来自 MSDN 的示例。了解 SortedList 如何期望 IComparer,但它们传入的是 ShapeAreaComparer :IComparer。 Shape 是“较大”类型(它位于被调用者的签名中,而不是调用者),但逆变允许“较小”类型(Circle)替换 ShapeAreaComparer 中通常采用 Shape 的任何位置。
希望有帮助。
Covariance is pretty easy to understand. It's natural. Contravariance is more confusing.
Take a close look at this example from MSDN. See how SortedList expects an IComparer, but they are passing in a ShapeAreaComparer : IComparer. The Shape is the "bigger" type (it's in the signature of the callee, not the caller), but contravariance allows the "smaller" type - the Circle - to be substituted for everywhere in the ShapeAreaComparer that would normally take a Shape.
Hope that helps.
在进入主题之前,让我们快速回顾一下:
基类引用可以保存派生类对象,但反之则不然。
协方差:
协方差允许您在需要基类型对象的地方传递派生类型对象
协方差可以应用于委托、泛型、数组、接口等。
逆变:
逆变应用于参数。它允许将带有基类参数的方法分配给需要派生类参数的委托,
请看下面的简单示例:
Before coming to topic, lets have a quick refresher:
Base class reference can hold a derived class object BUT not vice-versa.
Covariance:
Covariance lets you to pass a derived type object where a base type object is expected
Covariance can be applied on delegate, generic, array, interface, etc.
Contravariance:
Contravariance is applied to parameters. It allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class
Have a look at simple example below:
用乔恩的话来说:
我一开始发现他的解释令人困惑 - 但一旦强调了被替换,再结合 C# 编程指南中的示例,我就明白了:
转换器委托帮助我理解它:
TOutput
代表 <协方差,其中方法返回更具体的类型。TInput
表示逆变,其中向方法传递不太具体的类型。In Jons words:
I found his explanation confusing at first - but it made sense to me once to be substitued is emphasised, combined with the example from the C# programming guide:
The converter delegate helps me to understand it:
TOutput
represents covariance where a method returns a more specific type.TInput
represents contravariance where a method is passed a less specific type.让我们从协变和逆变示例中使用的类层次结构开始:
协变意味着您可以返回(输出)子类型的实例作为其超类型。这是一个
示例:
如前面的示例所示,打破协方差的一种方法是将超类型作为子类型返回。
另一方面,逆变意味着您可以输入子类型的实例作为其超类型。
它基本上是相同的,但对于输入来说,如下所示:
正如我们从前面的代码中看到的那样,适用相同的多态规则。我们可以使用子类型作为
一个超类型。
Let’s start with the class hierarchy we are using in the covariance and contravariance examples:
Covariance means you can return (output) the instance of a subtype as its supertype. Here is an
example:
As shown in the preceding example, one way to break covariance is to return a supertype as a subtype.
On the other hand, contravariance means you can input the instance of a subtype as its supertype.
It is basically the same thing but for inputs, like this:
The same polymorphic rule applies, as we can see from the preceding code. We can use a subtype as
a supertype.
我不得不认真思考如何很好地解释这一点。解释似乎和理解它一样困难。
假设您有一个基类 Fruit。你有两个子类 Apple 和 Banana。
您创建两个对象:
对于这两个对象,您可以将它们类型转换为 Fruit 对象。
您可以将派生类视为它们的基类。
但是,您不能将基类视为派生类,
让我们将其应用于列表示例。
假设您创建了两个列表:
您可以执行类似的操作...
因为
当您将它们添加到列表中时,它本质上是对它们进行类型转换。您可以这样想……
但是,将相同的逻辑应用于相反的情况会引发一些危险信号。
与
因为您不能像派生类一样对待基类,所以会产生错误。
以防万一您的问题是为什么这会导致错误,我也会对此进行解释。
这是 Fruit 类
,这是 Banana 类
因此,假设您再次创建了两个对象,
请记住 Banana 有两个变量“a”和“b”,而 Fruit 只有一个变量“a”。
所以当你这样做时......
你创建了一个完整的 Fruit 对象。
但如果你要这样做......
问题是你没有创建一个完整的 Banana 类。并非所有数据成员都被声明/初始化。
现在我洗完澡回来,给自己买了点零食,事情变得有点复杂。
事后看来,当进入复杂的东西时,我应该放弃这个隐喻
让我们创建两个新类:
它们可以做任何你喜欢的事情
现在让我们定义两个函数
这有点像“out”的工作方式你应该总是能够使用派生的类就好像它是基类一样,让我们将其应用到接口
out/in 之间的主要区别在于泛型用作返回类型或方法参数时,这是前一种情况。
让我们定义一个实现此接口的类:
然后我们创建两个对象:
如果您这样做:
您会收到类似“无法隐式转换自...”的错误
,您有两种选择,1)显式转换它们,或者,2)告诉编译器隐式转换它们。
或者
如果您的界面如下所示,则第二种情况会出现:
再次将其与两个函数相关联,
希望您看到情况如何逆转,但本质上是相同类型的转换。
再次使用相同的类
和相同的对象,
如果你尝试将它们设置为相等,
你的编译器会再次对你大喊大叫,你有与以前相同的选项,
或者
基本上在泛型仅用作返回类型时使用接口方法。当它将被用作方法参数时使用。使用委托时也适用相同的规则。
有一些奇怪的例外,但我不会在这里担心它们。
提前对任何粗心错误表示歉意=)
I had to think long and hard on how to explain this well. Explaining is seems to be just as hard as understanding it.
Imagine you have a base class Fruit. And you have two subclasses Apple and Banana.
You create two objects:
For both of these objects you can typecast them into the Fruit object.
You can treat derived classes as if they were their base class.
However you cannot treat a base class like it was a derived class
Lets apply this to the List example.
Suppose you created two Lists:
You can do something like this...
and
because it is essentially typecasting them as you add them into the list. You can think of it like this...
However, applying the same logic to the reverse case raises some red flags.
is the same as
Because you cannot treat a base class like a derived class this produces errors.
Just in case your question was why this causes errors I'll explain that too.
Here's the Fruit class
and here's the Banana class
So imagine that you again created two objects
remember that Banana has two variables "a" and "b", while Fruit only has one, "a".
So when you do this...
You create a complete Fruit object.
But if you were to do this...
The problem is that you don't create a complete Banana class.Not all the data members are declared / initialized.
Now that I'm back from the shower and got my self a snack heres where it gets a little complicated.
In hindsight I should have dropped the metaphor when getting into the complicated stuff
lets make two new classes:
They can do whatever you like
Now lets define two functions
This is kind of like how "out" works you should always be able to use a derived class as if it were a base class, lets apply this to an interface
The key difference between out/in is when the Generic is used as a return type or a method parameter, this the the former case.
lets define a class that implements this interface:
then we create two objects:
If you were do this:
You would get an error like "cannot implicitly convert from..."
You have two choices, 1) explicitly convert them or, 2) tell the complier to implicitly convert them.
or
The second case comes in to play if your interface looks like this:
relating it to the two functions again
hopefully you see how the situation has reversed but is essentially the same type of conversion.
Using the same classes again
and the same objects
if you try to set them equal
your complier will yell at you again, you have the same options as before
or
Basically use out when the generic is only going to be used as a return type of the interface methods. Use in when it is going to be used as a Method parameter. The same rules apply when using delegates too.
There are strange exceptions but I'm not going to worry about them here.
Sorry for any careless mistakes in advance =)
C# 4.0 中的协变和逆变都指使用派生类而不是基类的能力。 in/out 关键字是编译器提示,指示类型参数是否将用于输入和输出。
协
方差 C# 4.0 中的协方差由
out
关键字辅助,这意味着使用out
类型参数的派生类的泛型类型是可以的。因此,Apple
是Fruit
,因此List
可以安全地用作IEnumerable
逆变
由于 是
in
关键字,它表示输入类型,通常在委托中。原理是一样的,就是说委托可以接受更多的派生类。这意味着如果我们有一个
Func
,它可以转换为Func
。如果它们基本上是同一件事,为什么被称为协变/逆变呢?
因为即使原理相同,从派生到基的安全转换,当用于输入类型时,我们可以安全地将派生程度较低的类型 (
Func
) 转换为派生程度较高的类型 (Func
),这是有道理的,因为任何采用Fruit
的函数也可以采用Apple
。Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.
Covariance
Covariance in C# 4.0 is aided by
out
keyword and it means that a generic type using a derived class of theout
type parameter is OK. HenceSince
Apple
is aFruit
,List<Apple>
can be safely used asIEnumerable<Fruit>
Contravariance
Contravariance is the
in
keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.This means that if we have a
Func<Fruit>
, it can be converted toFunc<Apple>
.Why are they called co/contravariance if they are basically the same thing?
Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (
Func<Fruit>
) to a more derived type (Func<Apple>
), which makes sense, since any function that takesFruit
, can also takeApple
.让我分享一下我对这个话题的看法。
免责声明:忽略空赋值,我使用它们来保持代码相对较短,它们足以查看编译器想要告诉我们的内容。
让我们从类的层次结构开始:
现在定义一些接口,以说明
in
和out
泛型修饰符实际执行的操作:好的,那为什么还要使用带有
in 的接口呢?
和out
修饰符是否限制我们?让我们看看:不变性
让我们从不变性开始(没有
in
,没有out
修饰符)不变性实验
考虑
IInvariant
IInvariant.Get()
- 返回一个哺乳动物IInvariant.Set(Mammal)
- 接受一个哺乳动物如果我们尝试:
IInvariant<哺乳动物> invariantMammal = (IInvariant)null
?IInvariant.Get()
的人都期望得到一个 Mammal,但是IInvariant.Get()
- 返回一个 Animal。并非所有动物都是哺乳动物,因此它们不相容。IInvariant.Set(Mammal)
的人都期望可以传递 Mammal。由于IInvariant.Set(Animal)
接受任何动物(包括哺乳动物),因此它兼容如果我们尝试:
IInvariantinvariantMammal = (IInvariant)null
?IInvariant.Get()
的人会期望得到一个 Mammal,IInvariant.Get()
- 返回一个 Dog,每只 Dog是哺乳动物,因此它兼容。IInvariant.Set(Mammal)
的人都期望可以传递 Mammal。由于IInvariant.Set(Dog)
仅 接受狗(而不是所有哺乳动物都为狗),因此它不兼容。让我们检查一下我们是否正确
这个很重要:值得注意的是,这取决于泛型类型参数是否在类层次结构中较高或较低时,泛型类型本身由于不同原因不兼容。
好的,让我们看看如何利用它。
协方差 (
out
)当您使用
out
泛型修饰符时,您就具有协方差(见上文)如果我们的类型如下所示:
ICovariant;
,它声明了两件事:out
通用修饰符) - 这很无聊out
泛型修饰符施加的实际限制我们如何从
out
修饰符限制中受益?回顾一下上面“不变性实验”的结果。现在尝试看看当对协方差进行相同的实验时会发生什么?协方差实验
如果我们尝试一下会怎样:
ICovariantcovariantMammal = (ICovariant)null
?ICovariant.Get()
的人都期望得到一个 Mammal,但是ICovariant.Get()
- 返回一个 Animal。并非所有动物都是哺乳动物,因此它们不相容。ICovariant.Set(Mammal)- 由于out
修饰符限制,这不再是问题!如果我们尝试:
ICovariantcovariantMammal = (ICovariant)null
?ICovariant.Get()
的人会期望得到一个 Mammal,ICovariant.Get()
- 返回一个 Dog,每只 Dog是哺乳动物,因此它兼容。ICovariant.Set(Mammal)- 由于out
修饰符限制,这不再是问题!让我们用代码来确认它:
逆变(
in
)当你使用
in
泛型修饰符(见上文)如果我们的类型看起来像:
IContravariant
,它声明了两件事:in
通用修饰符) - 这很无聊in
施加的实际限制 > 泛型修饰符逆变实验
如果我们尝试一下会怎样:
IContravariant; contravariantMammal = (IContravariant)null
?- 由于IContravariant.Get()
in
修饰符限制,这不再是问题!IContravariant.Set(Mammal)
的人都期望可以传递 Mammal。由于IContravariant.Set(Animal)
接受任何动物(包括哺乳动物),因此它兼容如果我们尝试:
IContravariantcontravariantMammal = (IContravariant)null
?- 由于IContravariant.Get()
in
修饰符限制,这不再是问题!IContravariant.Set(Mammal)
的人都期望可以传递 Mammal。由于IContravariant.Set(Dog)
仅接受狗(而不是所有哺乳动物都为狗),因此它不兼容。让我们用代码来确认一下:
顺便说一句,这感觉有点违反直觉,不是吗?
为什么不两者都呢?
那么我们可以同时使用
in
和out
泛型修饰符吗? - 显然不是。为什么?回顾一下
in
和out
修饰符施加了哪些限制。如果我们想让泛型类型参数同时具有协变和逆变,我们基本上会说:T
T
>这本质上会使我们的通用接口非通用。
怎么记住呢?
你可以使用我的技巧:)
Let me share my take on this topic.
Disclaimer: ignore null assignments, I'm using them to keep the code relatively short and they are just enough to see what compiler wants to tell us.
Let's start with a hierarchy of classes:
Now define some interfaces, to illustrate what
in
andout
generic modifiers actually do:Ok, so why bother using interfaces with
in
andout
modifiers if they restrict us? Let's see:Invariance
Lets start with invariance (no
in
, noout
modifiers)Invariance experiment
Consider
IInvariant<Mammal>
IInvariant<Mammal>.Get()
- returns a MammalIInvariant<Mammal>.Set(Mammal)
- accepts a MammalWhat if we try:
IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null
?IInvariant<Mammal>.Get()
expects a Mammal, butIInvariant<Animal>.Get()
- returns an Animal. Not every Animal is a Mammal so it's incompatible.IInvariant<Mammal>.Set(Mammal)
expects that a Mammal can be passed. SinceIInvariant<Animal>.Set(Animal)
accepts any Animal (including Mammal), it's compatibleAnd what if we try:
IInvariant<Mammal> invariantMammal = (IInvariant<Dog>)null
?IInvariant<Mammal>.Get()
expects a Mammal,IInvariant<Dog>.Get()
- returns a Dog, every Dog is a Mammal, so it's compatible.IInvariant<Mammal>.Set(Mammal)
expects that a Mammal can be passed. SinceIInvariant<Dog>.Set(Dog)
accepts only Dogs (and not every Mammal as a Dog), it's incompatible.Let's check if we're right
THIS ONE IS IMPORTANT: It's worth noticing that depending on whether the generic type parameter is higher or lower in class hierarchy, the generic types themselves are incompatible for different reasons.
Ok, so let's find out how could we exploit it.
Covariance (
out
)You have covariance when you use
out
generic modifier (see above)If our type looks like:
ICovariant<Mammal>
, it declares 2 things:out
generic modifier) - this is boringout
generic modifierHow can we benefit from
out
modifier restrictions? Look back at the results of the "Invariance experiment" above. Now try to see what happens when make the same experiment for covariance?Covariance experiment
What if we try:
ICovariant<Mammal> covariantMammal = (ICovariant<Animal>)null
?ICovariant<Mammal>.Get()
expects a Mammal, butICovariant<Animal>.Get()
- returns an Animal. Not every Animal is a Mammal so it's incompatible.ICovariant.Set(Mammal)- this is no longer an issue thanks to theout
modifier restrictions!And what if we try:
ICovariant<Mammal> covariantMammal = (ICovariant<Dog>)null
?ICovariant<Mammal>.Get()
expects a Mammal,ICovariant<Dog>.Get()
- returns a Dog, every Dog is a Mammal, so it's compatible.ICovariant.Set(Mammal)- this is no longer an issue thanks to theout
modifier restrictions!Let's confirm it with the code:
Contravariance (
in
)You have contravariance when you use
in
generic modifier (see above)If our type looks like:
IContravariant<Mammal>
, it declares 2 things:in
generic modifier) - this is boringin
generic modifierContravariance experiment
What if we try:
IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null
?- this is no longer an issue thanks to theIContravariant<Mammal>.Get()
in
modifier restrictions!IContravariant<Mammal>.Set(Mammal)
expects that a Mammal can be passed. SinceIContravariant<Animal>.Set(Animal)
accepts any Animal (including Mammal), it's compatibleAnd what if we try:
IContravariant<Mammal> contravariantMammal = (IContravariant<Dog>)null
?- this is no longer an issue thanks to theIContravariant<Mammal>.Get()
in
modifier restrictions!IContravariant<Mammal>.Set(Mammal)
expects that a Mammal can be passed. SinceIContravariant<Dog>.Set(Dog)
accepts only Dogs (and not every Mammal as a Dog), it's incompatible.Let's confirm it with the code:
BTW, this feels a bit counterintuitive, doesn't it?
Why not both?
So can we use both
in
andout
generic modifiers? - obviously not.Why? Look back at what restrictions do
in
andout
modifiers impose. If we wanted to make our generic type parameter both covariant and contravariant, we would basically say:T
T
Which would essentially make our generic interface non-generic.
How to remember it?
You can use my tricks :)