正如另一个答案所示,在TList 版本中调用的方法将始终为IEnumerable<T>.GetEnumerator,即使它隐藏在TList 中并且另一个GetEnumerator 是可见的。
因此,即使TList 恰好是List<T>,版本B 也无法利用List<T>.Enumerator GetEnumerator(),并且枚举器结构将被装箱在对IEnumerator<T> IEnumerable<T>.GetEnumerator() 的调用中。
我们可以通过向后兼容的方式升级IEnumerable,如下所示:
interface IEnumerable<out T, out TEnumerator> : IEnumerable<T>
where TEnumerator : IEnumerator<T>
{
new TEnumerator GetEnumerator();
}
// In an imagined upgrade, the compiler should transform the iterator block
// to return IEnumerable<T, IEnumerator<T>>, allowing this to chain.
static IEnumerable<T> Flatten<T, TOuterEnumerator, TInnerEnumerator>
(this IEnumerable<IEnumerable<T, TInnerEnumerator>, TOuterEnumerator> collection)
// C# compiler needs to be reminded of these constraints,
// or foreach will not compile.
where TOuterEnumerator : IEnumerator<IEnumerable<T, TInnerEnumerator>>
where TInnerEnumerator : IEnumerator<T>
{
foreach (var subcoll in collection)
foreach (var elem in subcoll)
yield return elem;
}
IEnumerable<T, IEnumerator<T>> 将是IEnumerable<T> 的新自我,就像IEnumerable<object> 是IEnumerable 的新自我一样。
在这个想象的升级中,List<T> 应该实现IEnumerable<T, List<T>.Enumerator>。
编译器将扩展foreach 以使用TOuterEnumerator 和TInnerEnumerator 作为枚举数的静态类型,因此如果它们恰好是结构,则不会发生装箱。
请注意,编译器将始终选择IEnumerator<...>.MoveNext 和IEnumerator<...>.Current,即使枚举器类型将它们隐藏并具有另一个可见版本。这与非泛型方法不同,后者将选择可见版本,无论是IEnumerator<...> 还是特定类型。
这不会对任何理智的枚举器造成正确性问题(事实上,我不知道有任何枚举器显式地实现了IEnumerator<...>)。
这也不应该导致性能问题,因为编译器将使用枚举器的静态类型的知识来限制调用。
因此,如果枚举数是sealed class 或结构,则接口(虚拟)调用消失并由直接实例调用代替。
无耻的自我宣传:我有a blog entry on this。