IEnumerable<T>
を列挙済みにしたり、あるいはコピーとして使ったりするToArray
。
活躍の機会が多いだけに中身を確認しておく。
まず、IEnumerable<T>.ToArray
は拡張メソッドであり、Enumerable.ToArray<TSource>
に定義されている
参考:Source Browser
public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source) { if (source == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } return source is IIListProvider<TSource> arrayProvider ? arrayProvider.ToArray() : EnumerableHelpers.ToArray(source); }
シーケンスがIIListProvider<TSource>
か否かで処理が大別される。
このインターフェースはinternal
で、LINQ のイテレーターが実装している。
つまり、標準の LINQ メソッドの最適化のための分岐。
例:SelectEnumerableIterator<TSource, TResult>
Source Browser
それ以外は、EnumerableHelpers.ToArray<T>
に行く。
internal static T[] ToArray<T>(IEnumerable<T> source) { Debug.Assert(source != null); if (source is ICollection<T> collection) { int count = collection.Count; if (count == 0) { return Array.Empty<T>(); } var result = new T[count]; collection.CopyTo(result, arrayIndex: 0); return result; } LargeArrayBuilder<T> builder = new(); builder.AddRange(source); return builder.ToArray(); }
シーケンスがICollection<T>
の場合、長さを取って配列を作成し、まるっとコピーして返している。
それ以外の場合は、internal
なLargeArrayBuilder<T>
構造体に投げている。
ちなみに、IIListProvider<TSource>
の場合の処理でも、Select
やWhen
などの場合にこの構造体が使われている。
・LargeArrayBuilder<T>
Source Browser
アロケーションに関してはList<T>
と似ていて、要素の追加に応じてバッファ用の配列サイズを順次拡大していく。
AddRange
でシーケンスを渡しているが、この時点でICollection<T>
でないため逐次列挙されるので、要素数に応じたバッファ拡大 → コピーが繰り返される。
最後のLargeArrayBuilder<T>.ToArray
では、結果用に要素の長さの配列が新たにアロケーションされ、そこに内部バッファの内容がコピーされて返り値となる。
ということで、IEnumerable<T>.ToArray
の概略は以下のとおり。
- シーケンスの実体が
ICollection<T>
なら実質、配列のnew()
→CopyTo
- それ以外なら、バッファを用意して拡大しながらそこにシーケンスを列挙 → 最後に結果用の配列を作成し、コピー
以下、tips。
・ICollection<T>
でもコピーが行われる
当たり前だけどあらためて。
中身の型がコレクションで、単に型を列挙済みのものに変えたいだけならキャストする。
・IEnumerable<T>
だけれど長さが決まっているとか、あるいは一時データなので列挙したバッファを要素数に切り詰める必要がない、といった場合、自前でアロケーションしてそこに列挙すれば無駄がない
そこまでして早くしたいときは、そもそもIEnumerable<T>
を扱わないかもしれないけれど。