C# 编译器中的 Duck 类型
注意这不是关于如何在 C# 中实现或模拟鸭子类型的问题...
多年来,我一直认为某些 C# 语言功能依赖于数据语言本身定义的结构(对我来说,这总是像一个奇怪的先有鸡还是先有蛋的场景)。例如,我的印象是 foreach
循环只能与实现 IEnumerable
的类型一起使用。
从那时起,我开始了解到 C# 编译器使用鸭子类型来确定对象是否可以在 foreach 循环中使用,寻找 GetEnumerator
方法而不是 IEnumerable
。这很有意义,因为它去掉了鸡肉和鸡肉。鸡蛋难题。
我有点困惑为什么 using
块和 IDisposable
似乎不是这种情况。编译器无法使用鸭子类型并查找 Dispose
方法是否有任何特殊原因?造成这种不一致的原因是什么?
也许 IDisposable 背后还发生了其他事情?
讨论为什么永远拥有一个带有未实现 IDisposable 的 Dispose 方法的对象超出了本问题的范围:)
Note This is not a question about how to implement or emulate duck typing in C#...
For several years I was under the impression that certain C# language features were depdendent on data structures defined in the language itself (which always seemed like an odd chicken & egg scenario to me). For example, I was under the impression that the foreach
loop was only available to use with types that implemented IEnumerable
.
Since then I've come to understand that the C# compiler uses duck typing to determine whether an object can be used in a foreach loop, looking for a GetEnumerator
method rather than IEnumerable
. This makes a lot of sense as it removes the chicken & egg conundrum.
I'm a little confused as to why this doesn't seem to be the case with the using
block and IDisposable
. Is there any particular reason the compiler can't use duck typing and look for a Dispose
method? What's the reason for this inconsistency?
Perhaps there's something else going on under the hood with IDisposable?
Discussing why you would ever have an object with a Dispose method that didn't implement IDisposable is outside the scope of this question :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这里的
IDisposable
没有什么特别之处,但是迭代器有一些特别之处。在 C# 2 之前,在
foreach
上使用这种鸭子类型是实现强类型迭代器的唯一方式,也是无需装箱迭代值类型的唯一方式。我怀疑如果 C# 和 .NET 有泛型作为开始,foreach
将有 requiredIEnumerable
相反,并没有鸭子打字。现在,编译器在我能想到的其他几个地方使用了这种鸭子类型:
Add
重载(以及必须实现IEnumerable
IEnumerable 的类型) code>,只是为了表明它确实是某种集合);这允许灵活添加单个项目、键/值对等Select
等) - 这就是 LINQ 实现其灵活性的方式,允许针对多种类型使用相同的查询表达式格式,而无需更改 < code>IEnumerableGetAwaiter
返回具有IsCompleted
/ 的等待程序类型OnCompleted
/GetResult
在这两种情况下,这都使得将该功能添加到现有类型和接口变得更加容易,而该概念之前并不存在。
鉴于
IDisposable
自第一个版本以来就已存在于框架中,我认为鸭子键入using
语句不会有任何好处。我知道您在讨论中明确试图忽略使用Dispose
而不实现IDisposable
的原因,但我认为这是关键点。在语言中实现某个功能需要有充分的理由,我认为鸭子类型是一个超越支持已知接口的功能。如果这样做没有明显的好处,它最终不会出现在语言中。There's nothing special about
IDisposable
here - but there is something special about iterators.Before C# 2, using this duck type on
foreach
was the only way you could implement a strongly-typed iterator, and also the only way of iterating over value types without boxing. I suspect that if C# and .NET had had generics to start with,foreach
would have requiredIEnumerable<T>
instead, and not had the duck typing.Now the compiler uses this sort of duck typing in a couple of other places I can think of:
Add
overload (as well as the type having to implementIEnumerable
, just to show that it really is a collection of some kind); this allows for flexible adding of single items, key/value pairs etcSelect
etc) - this is how LINQ achieves its flexibility, allowing the same query expression format against multiple types, without having to changeIEnumerable<T>
itselfGetAwaiter
to return an awaiter type which hasIsCompleted
/OnCompleted
/GetResult
In both cases this makes it easier to add the feature to existing types and interfaces, where the concept didn't exist earlier on.
Given that
IDisposable
has been in the framework since the very first version, I don't think there would be any benefit in duck typing theusing
statement. I know you explicitly tried to discount the reasons for havingDispose
without implementingIDisposable
from the discussion, but I think it's a crucial point. There need to be good reasons to implement a feature in the language, and I would argue that duck typing is a feature above-and-beyond supporting a known interface. If there's no clear benefit in doing so, it won't end up in the language.没有先有鸡还是先有蛋的问题:
foreach
可能依赖于IEnumerable
,因为IEnumerable
不依赖于foreach
。未实现IEnumerable
的集合允许使用 foreach 的原因可能是 很大程度上是历史性的:此外,并非所有先有鸡还是先有蛋的问题实际上都是问题:例如,函数可以调用自身(递归!),或者引用类型可以包含自身(如链表)。
因此,当
using
出现时,为什么他们会使用像鸭子类型这样棘手的东西来指定,而他们可以简单地说:实现IDisposable
呢?从根本上说,通过使用鸭子类型,您正在围绕类型系统进行最终运行,这仅在类型系统不足以(或不切实际)解决问题时才有用。There's no chicken and egg:
foreach
could depend onIEnumerable
sinceIEnumerable
doesn't depend onforeach
. The reason foreach is permitted on collections not implementingIEnumerable
is probably largely historic:Furthermore, not all chicken and egg problems are actually problems: for example a function can call itself (recursion!) or a reference type can contain itself (like a linked list).
So when
using
came around why would they use something as tricky to specify as duck typing when they can simply say: implementIDisposable
? Fundamentally, by using duck typing you're doing an end-run around the type system, which is only useful when the type system is insufficient (or impractical) to address a problem.你问的问题不是先有鸡还是先有蛋的问题。它更像是语言编译器的实现方式。就像 C# 和 VB.NET 编译器的实现不同。如果你编写一个简单的 hello world 代码并使用编译器编译它并检查 IL 代码,它们将会有所不同。回到你的问题,我想解释一下 C# 编译器为
IEnumerable
生成了哪些 IL 代码。因此,C# 编译器针对
foreach
的情况进行了调整。The question which you are asking is not a chicken and egg situation. Its more like hows the language compiler is implemented. Like C# and VB.NET compiler are implemented differently.If you write a simple code of hello world and compile it with both the compiler and inspect the IL code they will be different. Coming back to your question, I will like to explain what IL code is generated by C# compiler for
IEnumerable
.So the C# compiler is tweaked for the case of
foreach
.