連載:[完全版]究極のC#プログラミング

Chapter15 LINQとクエリ式

川俣 晶
2010/03/17
Page 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

15.24 Anyメソッドと存在チェック

 クエリ式を日常的に書いていると、しばしば、あるクエリ式にヒットする要素が1つでもあるかどうかを調べたい場合がある。該当する要素が1つでもあれば機能させる、といった処理を記述することが比較的多いからである。

 この場合、安易に考えると上記のCountメソッドを使って、このメソッドの返却値が0よりも大きければ要素があるというコードを書きたくなる。しかし、これはお薦めしない。単に存在するか否かを調べるだけなら、「Anyメソッド」(対象が1つでもあるか調べる)のほうがお薦めである。実行速度が劇的に違うからである。その速度差を次のリスト15.30で実感していただきたい。

using System;
using System.Linq;

class Program
{
  static void Main(string[] args)
  {
    const int count = 1000000;
    int[] a = new int[1000];
    bool dummy;

    for (int i = 0; i < a.Length; i++) a[i] = i;

    DateTime start1 = DateTime.Now;
    for (int i = 0; i < count; i++)
    {
      dummy = (from n in a select n).Count() > 0;
    }
    Console.WriteLine(DateTime.Now - start1);

    DateTime start2 = DateTime.Now;
    for (int i = 0; i < count; i++)
    {
      dummy = (from n in a select n).Any();
    }
    Console.WriteLine(DateTime.Now - start2);
  }
}
リスト15.30 CountメソッドとAnyメソッドの速度差比較

00:00:43.6623658
00:00:00.2390239
リスト15.30の実行結果例
Pentium D搭載の筆者のPCによる、Visual Studio 2008 SP1のデバッグビルド。

 まさに圧倒的。100倍以上の差がついている。データ量が増えれば、この差はもっと広がることになる。

 さて、なぜこれだけの速度差が出るのだろうか? その理由は、Anyメソッドは列挙を打ち切ることができる点にある。要素の個数を数える処理は最後まで列挙をやめることはできない。しかし、“任意の要素が存在する”ことを調べるだけのAnyメソッドは、たった1つでも要素があればそこで列挙を打ち切ることができる。このケースであれば、列挙の最初の1個を取り出した時点で勝負がつく。残り999個の要素は調べる必要がない。

【C#olumn】クエリ式のデバッグテクニック

 ここでクエリ式をデバッグする際のヒントを簡単に説明しておこう。

 まず大前提として、1つのクエリ式にあらゆる処理を詰め込むことはやめよう。それは問題の切り分けの手間を増大させるほか、ソースコードのメインテナンス性も落とすことになり、良い選択とはいえない。

 次に、クエリ式の結果とする「型」についてだ。次のリスト15.31は、名前と点数のペアのデータから点数を抜き出して出力する例である。

using System;
using System.Linq;

class Program
{
  class Pair
  {
    public string Name;
    public int Point;
  }

  static void Main(string[] args)
  {
    Pair[] t =
    {
       new Pair() { Name = "太郎", Point = 100 },
       new Pair() { Name = "花子", Point = 100 },
    };

    foreach (var m in
                  from n in t
                  select n.Point)
    {
      Console.WriteLine(m);
    }
    // 出力:
    // 100
    // 100
  }
}
リスト15.31 結果だけを得るクエリ式

 このプログラムでは、出力される点数がおかしいと気づいたとき、Console.WriteLineの行でブレークさせて調べても、点数がわかるだけで誰の点数かはわからない。

 そこで、次のリスト15.32のように、点数ではなく、Pairクラスを結果の型としてみる。

foreach (var m in
              from n in t
              select n)
{
  Console.WriteLine(m.Point);
}
リスト15.32 結果の型を変える

 すると、Console.WriteLineの行でブレークさせたとき、点数だけでなく名前も知ることができる。

 少なくとも内蔵オブジェクトに対するクエリを行う「LINQ to Objects」である限り、結果が整数でも参照でも負荷の大きさは大差ないだろう。これは使ってよいテクニックだと思う(ただし、つねに良いかはわからない。良くないケースも多いだろう)。

 さて、実際にバグが発生して調べる場合は、トレース実行よりもブレークポイントが役に立つ。列挙と絡むため、個々の式が評価される回数が多く、[F11]キーを繰り返し押すだけではなかなか進まないからである。

 その際、ブレークポイントを仕掛けられる場所がどこか、正確に把握しておくとよい。たとえば、次の式を題材に見てみよう。

from n in t
where n >= 2
select n % 2 == 0 ? "even" : null

 この場合、ブレークポイントが設定可能な場所は次の3カ所だ。

  • クエリ式全体
  • 「n >= 2」部分
  • 「n % 2 == 0 ? "even" : null」部分

 ブレークポイントは、上記のいずれかの場所にキャレットを合わせて[F9]キーで設定できる。クエリ式全体に設定するときだけは、上記の2番目、3番目以外の場所で[F9]キーを押す。

 そして、ブレークポイントに設定できるブレーク条件の条件式も活用する必要がある。クエリ式は、クエリ式内部の式が計算される前後がブラックボックスであるため、その式に条件を付けて動作を捕まえるしかない。

 たとえば、上記の例でいえば、「n % 2 == 0 ? "even" : null」がnullになるタイミングで止めるには、この式にブレークポイントを付け、コード行の左側にあるブレークポイントの赤丸を右クリックして[条件]を選び、図15.2の画面のように条件式を指定する。


図15.2 条件付きのブレークポイントをLINQの条件部分に設定
左端にある赤丸を右クリックして[条件]を選択し、条件式として「null == (n % 2 == 0 ? "even" : null)」を指定する。

 これで、この式がnullになった瞬間、ここでプログラムはブレークする。そして、変数nの値を調べることができる。もちろん、直感的に式の意味を把握できるなら、もっと簡潔な条件式を書いてもよい(「n % 2 != 0」と書けば十分である)。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter15 LINQとクエリ式
    1.15.1 LINQの面白さ
    2.15.2 LINQとは何か?
    3.15.3 「値の集まり」に対する演算
    4.15.4 なぜLINQなのか?
    5.15.5 最も基本的なLINQ
    6.15.6 LINQの本質は列挙
    7.15.7 LINQを使ううえでの注意点
    8.15.8 クエリ結果を加工する
    9.15.9 複数のソースからクエリする
    10.15.10 条件で絞り込む
    11.15.11 一部項目のみselectする
    12.15.12 シンプルなソート
    13.15.13 クエリの接続
    14.15.14 クエリ結果のグループ化
    15.15.15 複数ソースを関連付けるjoin句
    16.15.16 from句とjoin句のパフォーマンス
    17.15.17 join句のグループ化結合
    18.15.18 join句の左外部結合
    19.15.19 単独で使うDefaultIfEmptyメソッド
    20.15.20 内部列挙を伴うfrom句の二重使用
    21.15.21 let句
    22.15.22 クエリのインスタンス化
    23.15.23 クエリ結果の個数を得る
  24.15.24 Anyメソッドと存在チェック/【C#olumn】クエリ式のデバッグテクニック
    25.15.25 まとめ/【C#olumn】LINQの難しさ/【Exercise】練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間