C# 编译器中的 Duck 类型

发布于 2024-11-15 20:07:51 字数 604 浏览 4 评论 0原文

注意不是关于如何在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

巷雨优美回忆 2024-11-22 20:07:51

这里的 IDisposable 没有什么特别之处,但是迭代器有一些特别之处。

在 C# 2 之前,在 foreach 上使用这种鸭子类型是实现强类型迭代器的唯一方式,也是无需装箱迭代值类型的唯一方式。我怀疑如果 C# 和 .NET 有泛型作为开始,foreach 将有 required IEnumerable 相反,并没有鸭子打字。

现在,编译器在我能想到的其他几个地方使用了这种鸭子类型:

  • 集合初始值设定项寻找合适的 Add 重载(以及必须实现 IEnumerableIEnumerable 的类型) code>,只是为了表明它确实是某种集合);这允许灵活添加单个项目、键/值对等
  • LINQ(Select 等) - 这就是 LINQ 实现其灵活性的方式,允许针对多种类型使用相同的查询表达式格式,而无需更改 < code>IEnumerable本身
  • C# 5 等待表达式需要 GetAwaiter 返回具有 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 required IEnumerable<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:

  • Collection initializers look for a suitable Add overload (as well as the type having to implement IEnumerable, just to show that it really is a collection of some kind); this allows for flexible adding of single items, key/value pairs etc
  • LINQ (Select etc) - this is how LINQ achieves its flexibility, allowing the same query expression format against multiple types, without having to change IEnumerable<T> itself
  • The C# 5 await expressions require GetAwaiter to return an awaiter type which has IsCompleted / 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 the using statement. I know you explicitly tried to discount the reasons for having Dispose without implementing IDisposable 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.

仙女山的月亮 2024-11-22 20:07:51

没有先有鸡还是先有蛋的问题:foreach 可能依赖于 IEnumerable,因为 IEnumerable 不依赖于 foreach。未实现 IEnumerable 的集合允许使用 foreach 的原因可能是 很大程度上是历史性的

在 C# 中,这并不是绝对必要的
用于继承的集合类
IEnumerable 和 IEnumerator 按顺序排列
与 foreach 兼容;只要
因为班级有所需的
GetEnumerator、MoveNext、重置和
现有成员,它将与
foreach。省略接口有
让您能够
定义 Current 的返回类型
比对象更具体,从而
提供类型安全。

此外,并非所有先有鸡还是先有蛋的问题实际上都是问题:例如,函数可以调用自身(递归!),或者引用类型可以包含自身(如链表)。

因此,当 using 出现时,为什么他们会使用像鸭子类型这样棘手的东西来指定,而他们可以简单地说:实现 IDisposable 呢?从根本上说,通过使用鸭子类型,您正在围绕类型系统进行最终运行,这仅在类型系统不足以(或不切实际)解决问题时才有用。

There's no chicken and egg: foreach could depend on IEnumerable since IEnumerable doesn't depend on foreach. The reason foreach is permitted on collections not implementing IEnumerable is probably largely historic:

In C#, it is not strictly necessary
for a collection class to inherit from
IEnumerable and IEnumerator in order
to be compatible with foreach; as long
as the class has the required
GetEnumerator, MoveNext, Reset, and
Current members, it will work with
foreach. Omitting the interfaces has
the advantage of allowing you to
define the return type of Current to
be more specific than object, thereby
providing type-safety.

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: implement IDisposable? 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.

江心雾 2024-11-22 20:07:51

你问的问题不是先有鸡还是先有蛋的问题。它更像是语言编译器的实现方式。就像 C# 和 VB.NET 编译器的实现不同。如果你编写一个简单的 hello world 代码并使用编译器编译它并检查 IL 代码,它们将会有所不同。回到你的问题,我想解释一下 C# 编译器为 IEnumerable 生成了哪些 IL 代码。

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}

因此,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.

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}

So the C# compiler is tweaked for the case of foreach.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文