为什么 C# 编译器在 foreach 语句中把字符串类单独处理?

21
我清楚地了解到使用C#编译器处理foreach语句时所采用的基于模式的方法"Pattern-based" approach。从C#语言规范(第8.8.4节)中可以明确得知,首先C#编译器会尝试查找GetEnumerator方法,然后才会查找IEnumerable<T>IEnumerable接口。
但是对我来说不清楚的是,为什么C#编译器将string单独处理(因为String类包含一个返回CharEnumeratorGetEnumerator方法,并且它还实现了IEnumerable<char>IEnumerable接口)。
string s = "1234";
foreach(char c in s)
  Console.WriteLine(c);

转换为

string s = "1234";
for(int i = 0; i < s.Length; i++)
  Console.WriteLine(s[i]);

但我在语言规范中找不到关于String类的任何异常。有人能给出这个解决方案的一些见解吗?

我尝试使用C# 4编译器。以下是先前代码片段的IL代码:

IL_0000:  ldstr       "1234"
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  stloc.2     
IL_0008:  ldc.i4.0    
IL_0009:  stloc.3     
IL_000A:  br.s        IL_001E
IL_000C:  ldloc.2     
IL_000D:  ldloc.3     
IL_000E:  callvirt    System.String.get_Chars
IL_0013:  stloc.1     
IL_0014:  ldloc.1     
IL_0015:  call        System.Console.WriteLine
IL_001A:  ldloc.3     
IL_001B:  ldc.i4.1    
IL_001C:  add         
IL_001D:  stloc.3     
IL_001E:  ldloc.3     
IL_001F:  ldloc.2     
IL_0020:  callvirt    System.String.get_Length
IL_0025:  blt.s       IL_000C

我的猜测是这是String类的内部优化(甚至是一些遗留代码)。在构建编译器时,您可以选择任何您想要的内部类方法。 - Sauleil
1个回答

21

很好的发现。我知道编译器对于数组执行类似的优化,但我不知道它也对字符串执行此操作。

我能为你提供的最好信息是来自语言规范的一个调用,它给予编译器权利偏离正统规范,只要它产生等效行为:

8.8.4 foreach语句

[...]以foreach (V v in x) 嵌入语句形式写出的foreach语句将被展开为:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
        while (e.MoveNext()) {
            v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

[...] 一种实现可以以不同的方式实现给定的foreach语句,例如出于性能原因,只要行为与上述扩展一致即可。


谢谢,Ani。你的解释看起来很合理。 - Sergey Teplyakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接