已经确定,编译器可以在迭代List或Array(请参阅Duck typing in the C# compiler)时执行鸭子类型处理,以消除一些开销,因为这些类型将其IEnumerator实现为堆栈分配的结构。
即使类型是通用类型,但必须限制实现IEnumerable时也是如此吗?
要提供更多的特异性,选项B的运行开销是否可以比A少?
A:
public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> collection)
{
foreach (var subCollection in collection)
foreach (var element in subCollection)
yield return element;
}
B:
public static IEnumerable<T> Flatten<TList, T>(this TList collection)
where TList : IEnumerable<IEnumerable<T>>
{
foreach (var subCollection in collection)
foreach (var element in subCollection)
yield return element;
}
答案 0 :(得分:6)
不,基本上。 “ B”的唯一用法是TList
本身实际上是struct
时;然后,IL可以使用“受约束的呼叫”来呼叫原始的GetEnumerator()
而无需将原始struct TList
值放入框内的任何部分。
但是:调用GetEnumerator()
之后,您将回到IEnumerator<T>
领域,并且不会使用自定义迭代器。
在这种情况下,所有这些都几乎没有意义,因为迭代器块也是 相当“可分配的”。因此...如果您担心避免TList
装箱,那么您大概会迷恋分配:在这种情况下,您也不会以这种方式编写迭代器块。
答案 1 :(得分:1)
正如另一个答案所表明的那样,在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。