【C#】LINQを使う際に陥りやすい思考の罠

このエントリーをはてなブックマークに追加

※主にLINQ to Objectsの話。LINQ to SQLなどは挙動が違うので注意。

LINQって便利!スタイリッシュ!C#を使い始めて一番感動したのはLINQでした。存在を知ってからしばらくは、いかにLINQを使いまくるか、ばっかり考えてました。しかし、この浅い考えは時に悲劇を生みます。

LINQは仕組みを理解した上で効果的に使うものです。今回はLINQを使う際に陥り易い思考の罠について書いてみます。

LINQを使ったコレクション操作

例えば、以下のようなint型のリストがあったとします。

1
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

これを以下のように順番に3つずつの固まりに分割するPartitionメソッドを考えます。

1
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

LINQには以下のメソッドが用意されています。

  • Take(int count) – 先頭から指定した分の要素を返す
  • Skip(int count) – 先頭から指定した分だけ飛ばして残りの要素を返す

これらを使って実装を考えると、例えばこんな感じ。

1
2
3
4
5
6
7
8
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
{
    while (source.Count() > 0)
    {
        yield return source.Take(size);
        source = source.Skip(size);
    }
}

おっ、すっきり書けた!使用例と結果は以下。

1
2
3
var list = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var list2 = list.Partition(3);
// [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

しかし実はこれはかなり効率が悪い。はっきり言ってとんでもない駄作です。この事実に気付けるようになるために、LINQの仕組みを理解しましょう。

IEnumerable<T>の使命

LINQはIEnumerable<T>インタフェースの拡張メソッドとして定義されています。List<T>やT[]の拡張メソッドではないことに注意!これ、めちゃめちゃ大事。なのでLINQを扱う際にはIEnumerable<T>の役割を理解することが、めちゃめちゃ大事。

IEnumerable<T>のMSDNには以下のように説明されています。

指定した型のコレクションに対する単純な反復処理をサポートする列挙子を公開します。

つまり…どういうことだってばよ?

簡単にIEnumerable<T>と他のコレクションの違いを説明します。

例えば、List<T>やT[]は指定したインデックスの要素を自由に取り出すことができます。

1
2
var fib = new[] { 1, 1, 2, 3, 5, 8, 13, 21 };
var fifth = fib[5]; // 8

また、Dictionary<T1, T2>はkeyを指定してvalueを取り出すことができます。

1
2
3
4
5
6
var urls = new Distionary<string, string> {
    {"twitter", "https://twitter.com/"},
    {"facebook", "https://www.facebook.com/"},
    {"google", "https://www.google.co.jp/"}
};
var twitterUrl = urls["twitter"]; // https://twitter.com/

これらはそれぞれのコレクションの使命、役割です。

ではIEnumerable<T>の使命は何なのか?それは要素を1つずつ取り出せること。それだけ。インデックスやkeyを指定して特定の要素を取り出すことはできず、「何か頂戴」に対して「次はこれね」と返すだけ。

IEnumerable<T>型の変数から中身を取り出す際にはforeachを使用します。

1
2
3
4
5
6
IEnumerable<int> fib = new[] { 1, 1, 2, 3, 5, 8, 13, 21 };
foreach (var item in fib) {
    Console.Write(item + ", "); // 1, 1, 2, 3, 5, 8, 13, 21
}

var fifth = fib[5]; // これはエラー(インデックスの指定は不可)

(現在続きを執筆中…)

Comments