C# 4.0 中的通用变体
C# 4.0 中的通用方差的实现方式使得可以毫无例外地编写以下内容(这就是 C# 3.0 中会发生的情况):
List<int> intList = new List<int>();
List<object> objectList = intList;
[非功能性示例:请参阅 Jon Skeet 的回答]
我最近参加了一次会议,Jon Skeet 对通用方差做了精彩的概述,但我不确定我是否完全理解了它 - 我理解 in
和 out
关键词,当谈到反对和协变时,但我很好奇幕后发生的事情。
执行此代码时,CLR 会看到什么?它是否隐式地将 List
转换为 List
出于兴趣,为什么在以前的版本中没有引入这一点,主要好处是什么 - 即现实世界的使用?
有关此的更多信息通用方差的帖子(但问题非常过时,正在寻找真实的最新信息)
Generic Variance in C# 4.0 has been implemented in such a way that it's possible to write the following without an exception (which is what would happen in C# 3.0):
List<int> intList = new List<int>();
List<object> objectList = intList;
[Example non-functional: See Jon Skeet's answer]
I recently attended a conference where Jon Skeet gave an excellent overview of Generic Variance, but I'm not sure I'm completely getting it - I understand the significance of the in
and out
key words when it comes to contra and co-variance, but I'm curious to what happens behind the scenes.
What does the CLR see when this code is executed? Is it implicitly converting the List<int>
to List<object>
or is it simply built in that we can now convert between derived types to parent types?
Out of interest, why wasn't this introduced in previous versions and what's the main benefit - ie real world usage?
More info on this post for Generic Variance (but question is extremely outdated, looking for real, up-to-date information)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
不,您的示例因以下三个原因而无法工作:
List
)是不变的;只有委托和接口是变体IEnumerable
(代码在 C# 3.0 和 4.0 中均无法编译 - 也不例外。)
所以这会 work:
CLR 仅使用引用,未更改 - 不会创建新对象。因此,如果您调用
objects.GetType()
,您仍然会得到List
。我相信它没有更早引入,因为语言设计者仍然必须弄清楚如何公开它的细节 - 它从 v2 开始就存在于 CLR 中。
其好处与您希望能够将一种类型用作另一种类型的其他情况相同。使用我上周六使用的相同示例,如果您有一些实现
IComparer
来按面积比较形状的工具,那么您不能使用它来对List< 进行排序,这太疯狂了;Circle>
- 如果它可以比较任意两个形状,那么它当然可以比较任意两个圆形。从 C# 4 开始,将存在从IComparer
到IComparer
的逆变转换,因此您可以调用circles.Sort(areaComparer)< /代码>。
No, your example wouldn't work for three reasons:
List<T>
) are invariant; only delegates and interfaces are variantIEnumerable<int>
toIEnumerable<object>
for example(The code fails to compile in both C# 3.0 and 4.0 - there's no exception.)
So this would work:
The CLR just uses the reference, unchanged - no new objects are created. So if you called
objects.GetType()
you'd still getList<string>
.I believe it wasn't introduced earlier because the language designers still had to work out the details of how to expose it - it's been in the CLR since v2.
The benefits are the same as other times where you want to be able to use one type as another. To use the same example I used last Saturday, if you've got something implements
IComparer<Shape>
to compare shapes by area, it's crazy that you can't use that to sort aList<Circle>
- if it can compare any two shapes, it can certainly compare any two circles. As of C# 4, there'd be a contravariant conversion fromIComparer<Shape>
toIComparer<Circle>
so you could callcircles.Sort(areaComparer)
.一些额外的想法。
正如 Jon 和其他人正确指出的那样,我们不会对类进行变化,而只会对接口和委托进行变化。所以在你的例子中,CLR什么也看不到;该代码无法编译。如果通过插入足够的强制转换来强制它进行编译,它会在运行时崩溃并出现错误的强制转换异常。
现在,询问方差在起作用时如何在幕后发挥作用仍然是一个合理的问题。答案是:我们将其限制为参数化接口和委托类型的引用类型参数的原因是这样在幕后什么都不会发生。当你说
幕后发生的事情时,对字符串的引用被插入到对象类型的变量中未经修改。构成对字符串的引用的位是对对象的引用的合法位,因此这里不需要发生任何事情。 CLR 不再将这些位视为引用字符串,而是开始将它们视为引用对象。
当你说:
同样的事情。什么也没发生。引用字符串枚举器的位与引用对象枚举器的位相同。当您进行强制转换时,还有更多的魔力发挥作用,例如:
现在 CLR 必须生成一个检查,以确保 e1 确实实现了该接口,并且该检查必须能够智能地识别差异。
但是我们可以摆脱只是无操作转换的变体接口的原因是因为常规赋值兼容性就是这样。你打算用 e2 做什么?
它返回的是对字符串的引用的位。我们已经确定它们无需更改即可与对象兼容。
为什么不早点介绍这个?我们还有其他功能要做,而且预算有限。
主要好处是什么?从字符串序列到对象序列的转换“正常工作”。
A few additional thoughts.
As Jon and others have correctly noted, we are not doing variance on classes, only interfaces and delegates. So in your example, the CLR sees nothing; that code doesn't compile. If you force it to compile by inserting enough casts, it crashes at runtime with a bad cast exception.
Now, it's still a reasonable question to ask how variance works behind the scenes when it does work. The answer is: the reason we are restricting this to reference type arguments that parameterize interface and delegate types is so that nothing happens behind the scenes. When you say
what happens behind the scenes is the reference to the string is stuck into the variable of type object without modification. The bits that make up a reference to a string are legal bits to be a reference to an object, so nothing needs to happen here. The CLR simply stops thinking of those bits as referring to a string and starts thinking of them as referring to an object.
When you say:
Same thing. Nothing happens. The bits that make a ref to a string enumerator are the same as the bits that make a reference to an object enumerator. There is somewhat more magic that comes into play when you do a cast, say:
Now the CLR must generate a check that e1 actually does implement that interface, and that check has to be smart about recognizing variance.
But the reason we can get away with variant interfaces being just no-op conversions is because regular assignment compatibility is that way. What are you going to use e2 for?
That returns bits that are a reference to a string. We've already established that those are compatible with object without change.
Why wasn't this introduced earlier? We had other features to do and a limited budget.
What's the principle benefit? That conversions from sequence of string to sequence of object "just work".
.NET 的第一个版本 (1.x) 根本没有泛型,因此泛型差异还很遥远。
应该注意的是,在.NET的所有版本中,都存在数组协方差。不幸的是,它是不安全的协变:
C# 4 中的协变和逆变是安全的,可以防止此问题。
很多时候,在代码中,您调用的 API 需要 Base 的放大类型(例如
IEnumerable
),但您所拥有的只是 Derived 的放大类型(例如IEnumerable;
)。在 C# 2 和 C# 3 中,您需要手动转换为
IEnumerable
,即使它应该“正常工作”。协变和逆变使其“正常工作”。ps 太糟糕了,斯基特的回答吃掉了我所有的代表点。该死的,斯基特! :-) 看起来他是 不过,之前已经回答过这个问题。
The first versions (1.x) of .NET didn't have generics at all, so generic variance was far off.
It should be noted that in all versions of .NET, there is array covariance. Unfortunately, it's unsafe covariance:
The co- and contra-variance in C# 4 is safe, and prevents this problem.
Many times in code, you are calling an API expects an amplified type of Base (e.g.
IEnumerable<Base>
) but all you've got is an amplified type of Derived (e.g.IEnumerable<Derived>
).In C# 2 and C# 3, you'd need to manually convert to
IEnumerable<Base>
, even though it should "just work". Co- and contra-variance makes it "just work".p.s. Totally sucks that Skeet's answer is eating all my rep points. Damn you, Skeet! :-) Looks like he's answered this before, though.