てくメモ

trivial な notes

【C#】IEnumerable<T>.ToArray の中身を確認

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>の場合、長さを取って配列を作成し、まるっとコピーして返している。

それ以外の場合は、internalLargeArrayBuilder<T>構造体に投げている。
ちなみに、IIListProvider<TSource>の場合の処理でも、SelectWhenなどの場合にこの構造体が使われている。

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>を扱わないかもしれないけれど。