C# 4.0 '动态' 和 foreach 语句 [英] C# 4.0 'dynamic' and foreach statement

查看:23
本文介绍了C# 4.0 '动态' 和 foreach 语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

不久之前我发现,新的 dynamic 关键字不能很好地与 C# 的 foreach 语句配合使用:

Not long time before I've discovered, that new dynamic keyword doesn't work well with the C#'s foreach statement:

using System;

sealed class Foo {
    public struct FooEnumerator {
        int value;
        public bool MoveNext() { return true; }
        public int Current { get { return value++; } }
    }

    public FooEnumerator GetEnumerator() {
        return new FooEnumerator();
    }

    static void Main() {
        foreach (int x in new Foo()) {
            Console.WriteLine(x);
            if (x >= 100) break;
        }

        foreach (int x in (dynamic)new Foo()) { // :)
            Console.WriteLine(x);
            if (x >= 100) break;
        }
    }
}

我期望对 dynamic 变量的迭代应该完全有效,就好像在编译时已知集合变量的类型一样.我发现第二个循环在编译时实际上是这样的:

I've expected that iterating over the dynamic variable should work completely as if the type of collection variable is known at compile time. I've discovered that the second loop actually is looked like this when is compiled:

foreach (object x in (IEnumerable) /* dynamic cast */ (object) new Foo()) {
    ...
}

并且每次访问 x 变量都会导致动态查找/强制转换,因此 C# 会忽略我在 foreach 语句中指定了正确的 x 类型 - 这对我来说有点令人惊讶...而且,C# 编译器完全忽略了来自动态类型变量的集合可能实现了 IEnumerable 接口!

and every access to the x variable results with the dynamic lookup/cast so C# ignores that I've specify the correct x's type in the foreach statement - that was a bit surprising for me... And also, C# compiler completely ignores that collection from dynamically typed variable may implements IEnumerable<T> interface!

完整的 foreach 语句行为在 C# 4.0 规范 8.8.4 Foreach 语句 文章中进行了描述.

The full foreach statement behavior is described in the C# 4.0 specification 8.8.4 The foreach statement article.

但是...完全有可能在运行时实现相同的行为!可以添加一个额外的 CSharpBinderFlags.ForEachCast 标志,将发出的代码更正为:

But... It's perfectly possible to implement the same behavior at runtime! It's possible to add an extra CSharpBinderFlags.ForEachCast flag, correct the emmited code to looks like:

foreach (int x in (IEnumerable<int>) /* dynamic cast with the CSharpBinderFlags.ForEachCast flag */ (object) new Foo()) {
    ...
}

并向 CSharpConvertBinder 添加一些额外的逻辑:

And add some extra logic to CSharpConvertBinder:

  • IEnumerable 集合和 IEnumerator 包装到 IEnumerable/IEnumerator.
  • Wrap 集合没有实现 Ienumerable/IEnumerator 来实现这个接口.
  • Wrap IEnumerable collections and IEnumerator's to IEnumerable<T>/IEnumerator<T>.
  • Wrap collections doesn't implementing Ienumerable<T>/IEnumerator<T> to implement this interfaces.

所以今天 foreach 语句迭代 dynamic 完全不同于迭代静态已知的集合变量,完全忽略用户指定的类型信息.所有这些都导致了不同的迭代行为(IEnumarble-实现集合仅作为 IEnumerable-实现进行迭代)并且超过 150x迭代 dynamic 时速度变慢.简单的修复会带来更好的性能:

So today foreach statement iterates over dynamic completely different from iterating over statically known collection variable and completely ignores the type information, specified by user. All that results with the different iteration behavior (IEnumarble<T>-implementing collections is being iterated as only IEnumerable-implementing) and more than 150x slowdown when iterating over dynamic. Simple fix will results a much better performance:

foreach (int x in (IEnumerable<int>) dynamicVariable) {

但是为什么我要写这样的代码?

But why I should write code like this?

很高兴看到有时 C# 4.0 dynamic 如果类型在编译时已知,它的工作方式完全相同,但很遗憾看到 dynamic在 IT 可以与静态类型代码相同的情况下工作完全不同.

It's very nicely to see that sometimes C# 4.0 dynamic works completely the same if the type will be known at compile-time, but it's very sadly to see that dynamic works completely different where IT CAN works the same as statically typed code.

所以我的问题是:为什么 foreachdynamic 上的工作方式与 foreach 在其他方面不同?

So my question is: why foreach over dynamic works different from foreach over anything else?

推荐答案

首先,向被问题困惑的读者解释一些背景:C# 语言实际上并不要求foreach"的集合实现<代码>IEnumerable.相反,它要求要么实现IEnumerable,要么实现IEnumerable或者只是它有一个GetEnumerator方法(和GetEnumerator 方法返回具有与预期模式相匹配的 Current 和 MoveNext 的内容,依此类推.)

First off, to explain some background to readers who are confused by the question: the C# language actually does not require that the collection of a "foreach" implement IEnumerable. Rather, it requires either that it implement IEnumerable, or that it implement IEnumerable<T>, or simply that it have a GetEnumerator method (and that the GetEnumerator method returns something with a Current and MoveNext that matches the pattern expected, and so on.)

对于像 C# 这样的静态类型语言来说,这似乎是一个奇怪的功能.我们为什么要匹配模式"?为什么不要求集合实现 IEnumerable?

That might seem like an odd feature for a statically typed language like C# to have. Why should we "match the pattern"? Why not require that collections implement IEnumerable?

在泛型之前先想想世界.如果您想制作整数集合,则必须使用 IEnumerable.因此,对 Current 的每次调用都会装箱一个 int,然后当然调用者会立即将它拆箱回 int.这很慢并且会给 GC 带来压力.通过使用基于模式的方法,您可以在 C# 1.0 中创建强类型集合!

Think about the world before generics. If you wanted to make a collection of ints, you'd have to use IEnumerable. And therefore, every call to Current would box an int, and then of course the caller would immediately unbox it back to int. Which is slow and creates pressure on the GC. By going with a pattern-based approach you can make strongly typed collections in C# 1.0!

现在当然没有人实现这种模式;如果你想要一个强类型集合,你实现 IEnumerable 就大功告成了.如果通用类型系统可用于 C# 1.0,那么匹配模式"功能就不太可能首先实现.

Nowadays of course no one implements that pattern; if you want a strongly typed collection, you implement IEnumerable<T> and you're done. Had a generic type system been available to C# 1.0, it is unlikely that the "match the pattern" feature would have been implemented in the first place.

正如您所指出的,在 foreach 中为动态集合生成的代码不是寻找模式寻找到 IEnumerable 的动态转换(然后从返回的对象进行转换当然,由 Current 到循环变量的类型.)所以你的问题基本上是为什么通过使用动态类型作为 foreach 的集合类型生成的代码无法在运行时查找模式?"

As you've noted, instead of looking for the pattern, the code generated for a dynamic collection in a foreach looks for a dynamic conversion to IEnumerable (and then does a conversion from the object returned by Current to the type of the loop variable of course.) So your question basically is "why does the code generated by use of the dynamic type as a collection type of foreach fail to look for the pattern at runtime?"

因为现在已经不是 1999 年了,即使回到 C# 1.0 时代,使用该模式的集合也几乎总是实现了 IEnumerable.真正的用户将编写生产质量的 C# 4.0 代码的可能性极低,该代码对实现模式但未实现 IEnumerable 的集合执行 foreach.现在,如果您处于这种情况,那是出乎意料的,很抱歉我们的设计未能预测您的需求.如果您认为您的场景实际上很常见,并且我们错误地判断了它的罕见程度,请发布有关您的场景的更多详细信息,我们会考虑在假设的未来版本中进行更改.

Because it isn't 1999 anymore, and even when it was back in the C# 1.0 days, collections that used the pattern also almost always implemented IEnumerable too. The probability that a real user is going to be writing production-quality C# 4.0 code which does a foreach over a collection that implements the pattern but not IEnumerable is extremely low. Now, if you're in that situation, well, that's unexpected, and I'm sorry that our design failed to anticipate your needs. If you feel that your scenario is in fact common, and that we've misjudged how rare it is, please post more details about your scenario and we'll consider changing this for hypothetical future versions.

请注意,我们生成到 IEnumerable 的转换是一个动态转换,而不是简单的类型测试.这样,动态对象就可以参与;如果它没有实现 IEnumerable 但希望提供一个代理对象,它可以自由地这样做.

Note that the conversion we generate to IEnumerable is a dynamic conversion, not simply a type test. That way, the dynamic object may participate; if it does not implement IEnumerable but wishes to proffer up a proxy object which does, it is free to do so.

简而言之,动态 foreach"的设计是动态地向对象请求一个 IEnumerable 序列",而不是动态地做我们在编译时会做的每一个类型测试操作".这在理论上确实违反了动态分析给出的结果与静态分析相同的设计原则,但在实践中,我们期望绝大多数动态访问的集合都能正常工作.

In short, the design of "dynamic foreach" is "dynamically ask the object for an IEnumerable sequence", rather than "dynamically do every type-testing operation we would have done at compile time". This does in theory subtly violate the design principle that dynamic analysis gives the same result as static analysis would have, but in practice it's how we expect the vast majority of dynamically accessed collections to work.

这篇关于C# 4.0 '动态' 和 foreach 语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆