もっと詳しく

例えば画面やファイルからの入力など、処理が終わるまで個数が確定できないものから配列データを作る手段として、Listが良く使われます。

プログラムでは頻繁に登場する使用頻度の極めた高いクラスですが、豊富な機能の中でごく一部しか使っていなかったりしませんか?

そこで今回は、Listを使ってやりたいことに焦点を当て、Listに実装されているメソッドを使った方法と、Linqを使った方法について紹介したいと思います。

また初心者にとっても、Listに関する一通りの知識を最初に理解しておくほうが、後々のプログラミングが書きやすくなりますので、是非この記事を活用して下さい。

Listで良く使われる機能一覧

まず最初に、Listで良く使われる機能を一覧に纏めました。

Listに実装されているメソッドを使う方法と、Listに対してLinqを使う方法の2通りを載せて言いますので、好みに合わせて使ってみて下さい。

機能(やりたいこと) メソッドを使う場合 Linqを使う場合 補足説明
要素の個数を取得する mylist.Count mylist.Count()
mylist.Count(ラムダ式)
✔100未満を返す場合、
mylist.Count(i=> i < 100)
✔ラムダ式を省略した場合、mylist.Countと同じ
先頭の要素を取り出す mylist[0]   mylist.First()
最後の要素を取り出す mylist[mylist.Count – 1]  mylist.Last()
指定した位置の要素を取り出す mylist[要素の番号] 該当なし
要素を1つ追加する mylist.Add(値) 該当なし
複数の要素を一括して追加する mylist.AddRange(配列) 該当なし
指定した位置にある要素を削除する mylist.RemoveAt(位置) 該当なし 同じ値が複数存在する場合
最初に見つかった値のみ
削除される
指定した要素を削除する mylist.Remove(値) 該当なし
指定した要素が含まれるか確認する mylist.Contains(値) 該当なし
指定した要素の位置を取得する mylist.IndexOf(値) 該当なし 同じ値が複数存在する場合
最初に見つかった値の位置が返される
指定した条件に一致する要素を検索する mylist.Find(ラムダ式) 該当なし 同じ値が複数存在する場合
最初に見つかった値が返る
要素の重複を無くした結果を取り出す 該当なし mylist.Distinct()
指定した条件を満たす要素を取り出す 該当なし mylist.Where(
ラムダ式
).ToList()
mylist.Where(i=> i == 100).ToList()
要素に任意の計算結果を適用して取り出す 該当なし mylist.Select(
ラムダ式
).ToList()
mylist.Where(i=> i *3)
.ToList()
要素の合計を求める 該当なし mylist.Sum()
要素の平均を求める 該当なし mylist.Average()
要素の最大値を求める 該当なし mylist.Max()
要素の最小値を求める 該当なし mylist.Min()
リストを配列に変換する 該当なし mylist.ToArray()
リストを辞書に変換する 該当なし mylist.ToDictionary(
 keyのラムダ式,
 valueのラムダ式
)
リストの要素をKeyとValueにセットするなら mylist.ToDictionary(i=>i)

Valueの値を数値に変換
するなら mylist.ToDictionary(i=>i,i=>i.ToString())

要素の並びを逆順にする mylist.Reverse() 該当なし
要素を昇順に並べ替える mylist.Sort() mylist.OrderBy(
ラムダ式
)
昇順で並べ替える例
mylist.OrderBy(i=>i)
要素を降順に並べ替える mylist.Sort()
mylist.Reverse()
mylist.OrderByDescending(
ラムダ式
)
降順で並べ替える例 mylist.ThenByDescending(i=>i)

ListのメソッドとLinqの速度比較

先ほど、好みに合わせて使ってくださいと申しましたが、大量のデータを扱ったり、ループの中で並べ替えやデータの取り出しを行う場合、ListのメソッドとLinqの速度差が気になるところです。

そこで、1万個の整数の要素を持つリストデータと、1万個の文字列の要素を持つリストデータを用意し、Listのメソッドを使った場合と、Linqを使った場合の処理速度を比較してみました。

上記データに対して、「100万回のループの中で処理を実行した際に掛かった時間を計測する」という行為を10回繰り返し、その平均値を秒で表記しています。

尚、このテストを行ったPCのスペックは、Core(TM) i5-9400 CPU @ 2.90GHz ,メモリ 32GBとなります。

計測結果は環境により異なるため、値を見るよりも、何倍遅いか(あるいは速いか)の方を参考にして頂ければと思います。

リストの件数を数えるCount

要素の個数をカウントする場合、Countプロパティを参照する場合に比べて、LinqのCount()を呼び出す方が2~3倍遅い結果になりました。

とは言え、ともに数ミリ秒程度であるため、この差はそれほど気にする必要は無さそうです。

しかし、Countの引数にラムダ式を使って、指定した条件の数をカウントした場合は、約1万~2万倍遅い結果になりました。

また、要素の型が文字列であった場合、更に1.5倍遅くなるようです。

データ型 Listプロパティ
count
Linq
Count()
Linq
Count(i=> i >= 0)
整数 0.0031 0.0071 87.5621
文字列 0.0031 0.0095 108.8519

先頭と末尾のデータ取り出し

先頭の要素を取り出す場合、Linqを使うと10倍くらい遅くなりました。

また、先頭の要素を取り出すよりも、末尾の要素を取り出す方が時間が掛かるようです。

末尾の要素を取り出す場合、先頭の要素を取り出す時と比べて両者の差は少なく、2.5倍ほど遅くなる程度でした。

データ型 Listの添え字指定
List[0]
Linq
First()
Listの添え字指定
List[Count-1]
Linq
Last()
整数 0.004 0.0183 0.0070 0.0168
文字列 0.004 0.0310 0.0066 0.0307

昇順及び降順ソート

並べ替えについては、要素が整数だった場合に比べて、文字列だった場合は223倍遅くなりました。

また、昇順より降順の方が、更に1.5倍程度遅くなっています。

昇順、降順のどちらにおいても、Linqの方が処理速度が9倍程度早くなるので、処理速度を稼ぎたい場合はLinqによる並べ替えを積極的に利用した方が良さそうです。

データ型 Listメソッド
Sort
Linq
OrderBy
Listメソッド
Sort&Revers
Linq
OrdrByDescending
整数 51.93 0.0184 89.330 0.0162
文字列 11596 0.0451 18571 0.0427

計測に使ったサンプルプログラムの例

以下のプログラムにおける forループ内の処理を適宜変更して計測しました。

void Measurement()
{ 
    Data = Enumerable.Range(0, Len).Select(i => i).ToList();
    Str = Enumerable.Range(0, Len).Select(i => string.Format("Data{0:0########}", i)).ToList();

    var dt = new DataTable();
    dt.Columns.Add("method1");
    dt.Columns.Add("method2");
    dt.Columns.Add("linq1");
    dt.Columns.Add("linq2");

    for (int n = 0; n < 10; n++)
    {
        var t1 = Test1(dt, Count);
        var t2 = Test2(dt, Count);
        var t3 = Test3(dt, Count);
        var t4 = Test4(dt, Count);
        DataRow dr = dt.NewRow();
        dr[0] = t1;
        dr[1] = t2;
        dr[2] = t3;
        dr[3] = t4;
        dt.Rows.Add(dr);
    }
    this.DataContext = dt;
}

public double Test1(DataTable dt,int cnt)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (int n = 0; n < cnt; n++)
    {
        var val = Data[0];
    }

    sw.Stop();
    return (double)sw.ElapsedMilliseconds / 1000d;
}
public double Test2(DataTable dt,int cnt)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (int n = 0; n < cnt; n++)
    {
        var val = Data[Str.Count - 1];
    }

    sw.Stop();
    return (double)sw.ElapsedMilliseconds / 1000d;
}
public double Test3(DataTable dt, int cnt)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (int n = 0; n < cnt; n++)
    {
        var val = Data.First();
    }

    sw.Stop();
    return (double)sw.ElapsedMilliseconds / 1000d;
}

public double Test4(DataTable dt, int cnt)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (int n = 0; n < cnt; n++)
    {
        var val = Data.Last();
    }

    sw.Stop();
    return (double)sw.ElapsedMilliseconds / 1000d;
}

まとめ

今回はListで良く使われる機能にを実現するために、Listに備わっているメソッドと、Linqを使った場合の両方について、一覧にまとめた結果を紹介しました。

また、ListのメソッドとLinqのどちらでも記述できる場合について、簡単ではありますが、両者の速度を比較し、グラフ化した結果を掲載しました。

どちらの場合も十分処理速度が速いので、少量のデータを使う場合はどちらを使っても問題ありませんが、大量のデータやループ処理の中で何度も行うケースにおいては、速度が速い方を使った方が無難です。

この記事が皆様のプログラミングの一助になれば幸いです。