C#4.0'动态'和foreach声明

con*_*low 11 ienumerable foreach dynamic c#-4.0

在我发现之前的很长一段时间,新的dynamic关键字与C#的foreach声明不兼容:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

foreach (object x in (IEnumerable) /* dynamic cast */ (object) new Foo()) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是......完全可以在运行时实现相同的行为!可以添加一个额外的CSharpBinderFlags.ForEachCast标志,修改被列出的代码,如下所示:

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

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

  • 包裹IEnumerable集合和/ IEnumerator到.IEnumerable<T>IEnumerator<T>
  • 包集合不实现Ienumerable<T>/ IEnumerator<T>实现此接口.

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

foreach (int x in (IEnumerable<int>) dynamicVariable) {
Run Code Online (Sandbox Code Playgroud)

但为什么我应该写这样的代码?

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

所以我的问题是:为什么foreach过度dynamic工作不同于foreach其他任何东西?

Eri*_*ert 23

首先,向那些对这个问题感到困惑的读者解释一些背景:C#语言实际上并不要求收集"foreach"工具IEnumerable.相反,它需要它实现IEnumerable,或者它实现IEnumerable<T>,或者只是它有一个GetEnumerator方法(并且GetEnumerator方法返回一个具有与预期模式匹配的Current和MoveNext的东西,依此类推.)

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

在仿制药之前考虑世界.如果你想创建一个int集合,你必须使用IEnumerable.因此,每次调用Current都会输入一个int,然后当然调用者会立即将其解包回int.这很慢并且会给GC带来压力.通过使用基于模式的方法,您可以在C#1.0中创建强类型集合!

如今当然没有人实现这种模式; 如果你想要一个强类型的集合,你实现IEnumerable<T>并且你已经完成了.如果C#1.0可以使用泛型类型系统,那么首先应该实现"匹配模式"功能.

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

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

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

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