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

Chapter6 ラムダ式(前編)

川俣 晶
2009/10/19

6.2 ラムダ式とは何か?

 すでに、いままでラムダ式を何回も使っているので軽く流そう。

 これまで使ってきたラムダ式とは名前のないメソッドであり、生成結果(=ラムダ式の記述内容)はそのままデリゲート型の変数や引数に入れて使う。つまり、呼び出しは、名前ではなく、デリゲート型の変数や引数などを経由して行う。

 ラムダ式の作成は特に難しいことはない。最も基本的な書き方は、次の形式だろう。

(引数の名前リスト)=>{ 実行内容 }

 リスト6.1に、実際に記述した例を示す。

using System;

delegate void MyAction(string message);

class Program
{
  static void Main(string[] args)
  {
    MyAction action = (message)=>
    {
      Console.WriteLine(message); // ラムダ式の内容
    };
    action("Hello! World!"); // 出力:Hello! World!
  }
}
リスト6.1 ラムダ式の記述例

 この例であれば、ラムダ式はデリゲートであるMyAction型の変数actionを経由して呼び出しを行っている。呼び出しそのものは、C# 1.x時代のデリゲート経由のメソッド呼び出しとなんら変わりはない。

 このプログラムを、ラムダ式を使わないで書き直すと次のリスト6.2のようになる。

using System;

delegate void MyAction(string message);

class Program
{
  static void SayMessage(string message)
  {
    Console.WriteLine(message);
  }

  static void Main(string[] args)
  {
    MyAction action = SayMessage;
    action("Hello! World!"); // 出力:Hello! World!
  }
}
リスト6.2 名前のあるメソッドでリスト6.1を書き直した例

 ラムダ式を使うと、“名前のあるメソッド”の中にラムダ式が埋め込まれる形になり、関連するコードがひとまとまりになる。しかし、ラムダ式を使わない場合は2つのメソッドが分離されてバラバラになっている(リスト6.2の場合ではSayMessageメソッドとMainメソッド)。メソッドの数が多いクラスであれば、この2つのメソッドははるか遠くに分かれて記述されるかもしれないが、それを関連付けて読むのは骨が折れる。

 なお、ラムダ式の実例については、「[補遺] ラムダ式を使用した事例」でも紹介しているので、参考にしてほしい。

【C#olumn】定義済みデリゲートを活用しよう

 筆者個人としては、用途ごとに個別のデリゲート型を宣言するほうがよいとは思うが(別個に宣言されていれば互換性があるとは見なされないので誤代入を防げるし、用途を連想しやすい名前を与えられる)、実際に、ありとあらゆる用途のデリゲートを個別に宣言すると、扱いにくいソースコードになりがちである。

 しかし、クラスライブラリには、Action、Funcなど、汎用的に使用できる便利なデリゲート型が定義されている。それらを活用して済むことも多いだろう。

 たとえば、次のリスト6.3はActionデリゲート(System名前空間)を使うことで、デリゲート型の宣言をソースコードから排除できる。

using System;

class Program
{
  static void Main(string[] args)
  {
    Action<string> action = delegate(string message)
    {
      Console.WriteLine(message);
    };
    action("Hello! World!"); // 出力:Hello! World!
  }
}
リスト6.3 Actionを使ってdelegate型宣言を排除した例

 Action<string>デリゲートは、文字列を引数に取るvoid型のメソッドに対して使用できる、クラスライブラリで定義済みのデリゲート型である。

 このように、クラスライブラリが提供する基本的なデリゲート型が活用できれば型の定義の手間が軽減される。ここでは実際に、そのような目的で使用できるデリゲート型をいくつか紹介しよう。

■MethodInvokerデリゲート(System.Windows.Forms名前空間)

 引数なし、戻り型voidに限って使用できるデリゲート。なんと.NET Framework 1.0から存在する最古参のデリゲート型。しかし、System.Windows.Forms.dllを参照しなければ利用できないため、使えないケースが多い。古いソースコードでは使われている可能性があるので、名前だけは覚えておくとよいだろう。新規に使うことは、あまりお薦めしない。

■Actionデリゲート(System名前空間)

 引数なし、戻り型voidに限って使用できるデリゲート。誰もが参照するSystem.Core.dllに含まれているので、MethodInvokerの代わりに汎用的に使用できる。しかし、.NET Framework 3.5からのサポートなので、C# 3.0+.NET Framework 2.0で開発するような場合には使えないジレンマもある。

■Action<T>ジェネリックデリゲート(System名前空間)

 引数(T)を1つ持ち、戻り型voidのジェネリックなデリゲート型。コレクション関係でもよく使われる。コレクションの列挙で呼び出すメソッドを指定するデリゲート型ならこれで十分。.NET Framework 2.0以降なら使用できるので、基本中の基本。実は、「変数のキャプチャ」を併用することで、これを使えば済むケースは多い。

■Action<T1, T2>ジェネリックデリゲート(System名前空間)
■Action<T1, T2, T3>ジェネリックデリゲート(System名前空間)
■Action<T1, T2, T3, T4>ジェネリックデリゲート(System名前空間)

 引数を2〜4個持ち、戻り型voidのジェネリックなデリゲート型。Action<T>の仲間に見えるが、実はAction<T>と違って、.NET Framework 3.5以降でのみサポートとなっている。

■Predicate<T>ジェネリックデリゲート(System名前空間)

 引数(T)を1つ持ち、戻り型boolのジェネリックなデリゲート型。基本的にはAction<T>と同様だが、戻り型がbool型であるため、結果を呼び出し元に伝達できる点で異なっている。たとえばコレクションから検索を行うようなケースでは、この型が使われる。引数で渡した値が意図した値であれば、戻り値をtrueにして発見/検索終了を伝達できる。しかし、出番が多いかと思いきや、次に紹介するFuncジェネリックデリゲートで戻り値型にboolを指定して使用するケースのほうが多いようである。

■Func<TResult>ジェネリックデリゲート(System名前空間)
■Func<T, TResult>ジェネリックデリゲート(System名前空間)
■Func<T1, T2, TResult>ジェネリックデリゲート(System名前空間)
■Func<T1, T2, T3, TResult>ジェネリックデリゲート(System名前空間)
■Func<T1, T2, T3, T4, TResult>ジェネリックデリゲート(System名前空間)

 デリゲート型の最終兵器。戻り値と引数の型を自由に設定できる究極のカスタマイズ可能デリゲート。引数の数は0〜4個のバリエーションがあり、少なくとも引数が4個以内であればあらゆる用途に対応できる。たとえば、

delegate int MyDelegate( string n, double d );

という宣言を行う代わりに、

Func<string, double, int>

というデリゲートで代用することができる(ただし、この2つは異なる型になるので相互に代入はできない。どちらか片方を使うと決めたら、それを貫徹しなければならない)。

 唯一の死角は、戻り値がvoid型のケースに対応できないことだろう。この場合はActionという名を持つデリゲートの各バリエーションを使用して対処する。

 これ以外には特に問題はないが、強いて弱点を挙げるなら、.NET Framework 3.5からのサポートなので、それよりも古いフレームワークを対象とした場合に使用できないこと、および、記述に冗長感があるところだろう。この2つの理由は、依然としてPredicate<T>を使う価値があることを示している。使用できる条件は限られているが、.NET Framework 2.0以降なら使用できるし、戻り型をいちいち明示しないので冗長感も薄くなる。

 以上のような型を活用して迅速にラムダ式を使うコードを記述していくことができる。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter6 ラムダ式(前編)
    1.6.1 おかずでもデザートでもなく“ご飯”
  2.6.2 ラムダ式とは何か?/【C#olumn】定義済みデリゲートを活用しよう
    3.6.3 ラムダ式は上位スコープにアクセスできる
    4.6.4 キャプチャされる変数
    5.6.5 注意を要するキャプチャの本質
    6.6.6 デリゲートの共変性と反変性
    7.6.7 デリゲートインスタンスの等価性
    8.6.8 ラムダ式で継承を置き換えてみる
    9.6.9 C# 2.0と匿名メソッド/【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 記事ランキング

本日 月間