【LINQの前に】ラムダ式?デリゲート?Func<T, TResult>?な人へのまとめ【知ってほしい】

  • 667
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

  • 引数にラムダ式を渡す
  • ラムダ式の型はFuncとActionが用意されている
  • ラムダ式はデリゲートを作るためのもの

 このような記述や説明をしているC#関連のブログをたまに見かけます。実はこれらは正確には違ったり、正しくなかったり、ちょっと足りなかったりします。「先ほどの説明は正しいのではないか?」と思った方、ぜひこの投稿を読んでください。ラムダ式やその関連要素についての理解が深まると思います。

 そして、「LINQを勉強したいのだけれど、ラムダ式とかFunc<T, TResult>とかわからん!」、「Func<TSource, bool>型の引数をとるメソッドに、『num => num > 0』とラムダ式を書いているけれど、それがどういう仕組みか良くかわからない...」という方、ぜひこの投稿を読んでください。わからないことが理解できたり、疑問がとけると思います。

 この投稿では次のC#の要素とそれらの関係について説明します。

  • デリゲート
  • Func<T, TResult>
  • 匿名関数
  • ラムダ式

 MSDNのラムダ式の説明は、次の一文で始ります。

ラムダ式は、デリゲート型または式ツリー型を作成するために使用できる匿名関数です。

 この投稿で紹介する、「ラムダ式」、「デリゲート」、「匿名関数」が出てきましたね。この投稿を最後まで読んで頂いたら、この一文が意味する事が理解できると思います。

補足

 本投稿では、それぞれの要素を深く説明するのではなく、LINQやReactive Extensionsを勉強する上で必要な「それぞれがどういうものなのか」と、「それらの関連」を中心に説明します。そのため、デリゲートの結合などLINQなどを使う上で関連が薄いものは触れません。また、本投稿では現在では非推奨な記述方法も紹介します。

先にまとめ

  • デリゲートはメソッド参照する型
  • デリゲート型は自分でも作れる
  • LINQなどでよく使うFunc<T, TResult>はたくさんあるデリゲートの型の内の1つ
  • デリゲートを作るためだけに、クラスメソッドかインスタンスメソッドを定義するのは面倒で読みづらい
  • 匿名関数を使えばメソッドを定義せずともインラインに処理を書いてデリゲートを作る事ができる
  • 匿名関数には2種類あってその1つがラムダ式
  • ラムダ式はデリゲート型の生成だけでなく、式ツリー型の生成にも使える
  • 匿名関数はコンパイラがクラスメソッドやインスタンスメソッド、クラスなどを内部的に作って、それを参照するデリゲートを作っている

デリゲートについて

 それではまずデリゲートとは何かを紹介します。

「デリゲートなんかよりも、ラムダ式を使いこなしたい!早くラムダ式を教えろ」と思う方もいるかもしれません。

 しかし、LINQやReactive Extensionsで、ラムダ式を使うためにデリゲートを理解しておくことは非常に重要です。

デリゲートって何?

 C#におけるデリゲートとは、メソッドを参照する型です。

 C#では、メソッドを引数として渡したり返り値として返すのではなく、メソッドを参照するデリゲートを、引数として渡したり返り値として返します。デリゲートの存在がLINQやReactive Extensionsを支えています。

 全てのデリゲートの型はDelegate型から派生した型です。もちろんデリゲートの型を自分で作ることができます。

デリゲートの型を自分で作る

 それではデリゲートの型を自分で作ってみましょう。デリゲートの型はDelegate型から派生するのでしたね。普通のクラスのようにDelegateを継承すればいいのでしょうか?これは実はできません。コンパイラとシステムだけしか、Delegate型とそのサブクラスのMulticastDelegate型を明示的に継承できません。

 デリゲートの型を作るためには普通にDelegateを継承するのではなく、次のように「delegate」キーワードを使います。

デリゲート型の宣言例(1)
// intを引数にとってintを返すメソッドを参照するデリゲート型
public delegate int IntToInt(int value);

 こうすることで、Delegate型(実際はMulticastDelegate型)のサブクラスが定義されます。

 上記のデリゲート型は、「intを引数にとってintを返すメソッドを参照するデリゲート型」です。

 他にもいくつか例を示します。

デリゲート型の宣言例(2)
// stringを引数にとってintを返すメソッドを参照するデリゲート型
public delegate int StringToInt(string value);

// 引数をとらずintを返すメソッドを参照するデリゲート型
public delegate int ReturnInt();

// 引数にintをとりオブジェクトを返さないメソッドを参照するデリゲート型
public delegate void ActionInt(int value);

// 引数に二つのintをとってintを返すメソッドを参照するデリゲート型
public delegate int IntIntToInt(int value0, int value1);

クラスメソッドを参照するデリゲートのインスタンスを作る

 さてデリゲートの型を作ったので、次はデリゲートのインスタンスを作りましょう。

 次のデリゲート型で、

インスタンスを作るデリゲート型
// intを引数にとってintを返すメソッドを参照するデリゲート型
public delegate int IntToInt(int value);

 次のクラスメソッドを参照する

参照するクラスメソッド
public class Calculator
{
     public static int AddOne (int value)
     {
          return value + 1;
     }
}

 デリゲートのインスタンスを作るには、次のように書きます。

// AddOneメソッドを参照するIntToInt型の
IntToInt addOne = Calculator.AddOne;

// Calculatorクラスの中ならば、以下でもOK
// IntToInt addOne = AddOne;

 こう書くとAddOneメソッドを参照するIntToIntメソッドを定義することが可能です。これでOKです。

 「え、これでいいの?しっくりこないなぁ」という方、今は書くべきでないC# 2.0以前の書き方を示します。

// C# 2.0以前の書き方
// 【注意】C# 1.0/1.1でなければ、こういう書き方はすべきではありません!
IntToInt addOne = new IntToInt (Calculator.AddOne);

 前者の書き方で腑に落ちない方でも、後者の書き方をみれば納得できるのではないでしょうか?前者の書き方はC# 2.0で可能になり、デリゲートのインスタンス生成の記述が簡潔になりました。あくまで理解の促進の為に後者を示しましたが、クラスメソッドやインスタンスメソッドを参照するデリゲートを生成する際は前者を使ってくださいね。

インスタンスメソッドを参照するデリゲートのインスタンスを作る

 さきほどクラスメソッドを参照するデリゲートを作りました。もちろんインスタンスメソッドを参照するデリゲートを作ることが可能です。

Multiplier
public class Multiplier
{
     readonly int number;

     public Multiplier (int number)
     {
          this.number = number;
     }

     public int Calc (int v)
     {
          return number * v;
     }
}

 このMultiplierクラスのインスタンスのCalcメソッドを参照するIntToIntデリゲート型のインスタンスを作るには、次のように書きます。

Multiplier doubler = new Multiplier (2); // Calcメソッドは引数で渡した数の2倍の数を返す
IntToInt doublerIntToInt = doubler.Calc; // doublerのCalcを参照するデリゲートを生成

Multiplier trippler = new Multiplier (3); // Calcメソッドは引数で渡した数の3倍の数を返す
IntToInt tripplerIntToInt = trippler.Calc; // tripplerのCalcを参照するデリゲートを生成

 デリゲートがインスタンスメソッドを参照するならば、どのインスタンスのメソッドを参照しているのかの情報も必要ですよね。デリゲートは対象のインスタンスへの参照も内部的に保持しています。そのインスタンスにアクセスするためのプロパティとして、Delegate型はTargetプロパティを持っています。

この節のまとめ

  • デリゲートはメソッドを参照する型
  • デリゲートは引数として渡したり、返り値として返すことができる
  • デリゲートは型がある
  • デリゲートの型をdelegateキーワードを用いて自作することができる
  • デリゲートはクラスメソッドを参照することができる
  • デリゲートはインスタンスメソッドを参照することができる

※この投稿ではデリゲートの結合についてや更に詳しいデリゲート内部のことには触れません

Func<T, TResult>について

 この節ではLINQやRxなどで登場頻度の多い「Func<T, TResult>型」を説明します。

Func<T, TResult>はジェネリックなデリゲートの型

 前節ではデリゲートに関して説明し、その中でデリゲートには型があることも述べました。

 Func<T, TResult>は、T型の引数を取り、TResult型を返すメソッドを参照するジェネリックなデリゲート型です。非常に汎用的に使うことが可能です。これは、たくさんあるデリゲートの型の中のうちの一つです。

 Func<T, TResult>型のデリゲートのインスタンスを生成する例を示します。

public static int AddOne (int value)
{
     return value + 1;
}

public static int GetLength (string value)
{
     return value.Length;
}

public static void Main (string[] args)
{

     // intを引数に取りintを返すAddOneメソッドを参照するデリゲートを生成
     Func<int, int> addOneDelegate = AddOne;

     // stringを引数に取りintを返すGetLenghtメソッドを参照するデリゲートを生成
     Func<string, int> GetLengthDelegate = GetLength;

     // doubleを引数に取りdoubleを返すSystem.Math.Absメソッドを参照するデリゲートを生成
     Func<double, double> doubleFloatDelegate = Math.Abs;
}

 Func<T, TResult>はジェネリックなデリゲート型なので、適切に型パラメータを設定すれば、様々なメソッドを参照できます。

Func<T, TResult>以外の汎用的なジェネリクなデリゲート型

 Func<T, TResult>型は引数を1つ取り返り値を返すメソッドを参照するデリゲートです。Func<T, TResult>型では、引数を取らず返り値のみ返すメソッドや、複数の引数を取って返り値を返すメソッド、そして返り値を返さないメソッドなどを参照できません。

 上記のようなメソッドを参照することができる汎用的なジェネリクなデリゲート型もあります。一例を挙げます。

  • Action型、引数とらず返り値も返さないメソッドを参照するデリゲートの型
  • Action<T>型、T型の引数を取って返り値を返さないメソッドを参照するデリゲートの型
  • Action<T1,T2>型、T1型、T2型の引数を取って返り値を返さないメソッドを参照するデリゲートの型
  • Func<TResult>型、引数とらずTResult型の返り値を返すメソッドを参照するデリゲートの型
  • Func<T1,T2,TResult>型、T1型、T2型の引数を取りTResult型の返り値を返すメソッドを参照するデリゲートの型

 これらはあくまで一例です。さらに多くの引数をとるAction系・Func系のデリゲートの型があります。これらの汎用的なジェネリクなデリゲート型は、Action<T>型をのぞきC# 3.0/.NET 3.5で追加されました。

Func系・Action系以外のデリゲート型もある

 LINQやRxなどが利用していて、実際によく使うのはFunc系やAction系ですが、「デリゲートの型がFunc系・Action系しか存在しない」というのは間違いです。Func系・Action系以外のデリゲート型も、もちろんクラスライブラリに存在します。

 Predicate<T>型というデリゲート型もその1つです。このデリゲート型は、ListクラスのFindAllメソッドの引数として利用されます。他にもListのConvertAllメソッドなどで使われている、Convertor<TInput, TOutput>型や同じくListのSortメソッドで使われているComparison<T>型などがあります。

 実際に使うのはFunc系・Action系のデリゲート型が多いですが、それ以外のデリゲート型もあるのに注意してください。

この節のまとめ

  • Func<T, TResult>型はたくさんあるデリゲート型のうちの一つ
  • Func<T, TResult>型は引数を一つとって返り値を返すメソッドを参照するジェネリクなデリゲートの型
  • 返り値の有無、引数の個数が違う他のFunc系やAction系のデリゲート型がある
  • Func系やAction系意外のデリゲート型もある

匿名関数について

 ここまででデリゲートとその代表的な型の一つであるFunc<T, TResult>を紹介しました。

 この節でもまだラムダ式はで出てきません。ところでラムダ式があると何が嬉しいのか、そしてラムダ式がないとどう不便なのか考えたことはありますか?この節では、匿名関数という概念を介してそれを説明します。この節を読む上でのポイントは、

  • LINQなどでよく使うFunc<T, TResult>型はデリゲートの型の一つ
  • デリゲートはメソッドを参照する型
  • この投稿では今のところ、デリゲートを作る方法はインスタンスメソッドとクラスメソッドしか紹介していない

 です。ここを抑えた上で続きを読んでください。

デリゲートの利用例

 匿名関数を説明するためのサンプルコードを示します。List<T>型とFunc<T, TResult>を引数に取るCountListメソッドです。

 第一引数List<T>型のlistの要素のうち、第二引数Func<T, bool>型のpredicateを適用して真になる要素の数を数えるメソッドです。

public static int CountList<T>(List<T> list, Func<T, bool> predicate)
{
    int count = 0;
    foreach (T element in list){
        // predicateが参照するメソッドを、elementを引数に渡し呼び出している
        if(predicate(element)){ 
            // 真ならカウンタをインクリメント
            count++;
        }
    }
    return count;
}

 コメントにも書きましたが、

predicate(element)

 という箇所では、T型のelementを引数に、Func<T, bool>型のデリゲートpredicateが参照しているメソッドを呼び出しています。ちなみに、

predicate.Invoke(element)

 とも書けますが冗長ですね。

匿名関数がないとどう不便か

 それでは匿名関数が無いとどう不便か説明します。

 先ほど作ったCountListメソッドを使ってList<string>型namesの要素の内、文字列の長さが5未満のものを数えます。引数としてList<string>型とFunc<string, bool>型のデリゲートを渡せばいいですね。

public static bool IsLengthLessThan5 (string str)
{
     return str.Length < 5;
}

public static void Main (String[] args)
{
     List<string> names = new List<string>
     {
          "Taro",
          "Jiro",
          "Saburo",
          "Umeko",
          "Takeko",
          "Matsuko",
     };

     // IsLengthLessThan5を参照するFunc<string, bool>型のデリゲートを生成
     Func<string, bool> predicate = IsLengthLessThan5;

     int count = CountList(names, predicate);
}

 コンパイルは通りますし挙動も正しいです。しかしこの「もし匿名関数がない場合、こう書かなければいけないならないコード」はあまりよくありません。

 デリゲートをつくるためだけに使うIsLengthLessThan5メソッド。このメソッドがデリゲートのインスタンスを作る箇所と離れた場所で書かれているからです。わざわざデリゲートをつくるためだけにメソッドを用意するのは面倒ですし、読む際もいったりきたりして面倒ですね。

 デリゲートのインスタンスを生成する場所にインラインで手軽にメソッドを定義できたら便利だと思いませんか?次のコードのようなイメージで、です。

public static void Main (String[] args)
{
     List<string> names = new List<string>
     {
          "Taro",
          "Jiro",
          "Saburo",
          "Umeko",
          "Takeko",
          "Matsuko",
     };

     Func<string, bool> predicate = /* predicateをつくるためのメソッドをここで定義できたら読みやすい・書きやすい */ 

     int count = CountList(names, predicate);
}

匿名関数でインラインでデリゲートを生成

 C# 2.0以前、デリゲートの生成はインスタンスメソッドかクラスメソッドを参照して生成する方法しかありませんでした。

 これに加えてC# 2.0で匿名関数(Anonymous Functions)でデリゲートを生成できるようになりました。

 この匿名関数によりインラインで処理を記述してデリゲートを生成できるようになりました。匿名関数を使えばデリゲートを生成するためだけのメソッドがコード中で散乱することも、コードを読んでいる時にデリゲートを生成している箇所と参照先のメソッドといったりきたりすることもありません。

 C#において匿名関数は二通りあります。

  • 匿名メソッド(C# 2.0から)
  • ラムダ式(C# 3.0から)

です。次節ではこれらについて説明します。

この節のまとめ

  • 匿名関数を使わないと、インスタンスメソッドやクラスメソッドを参照してデリゲートのインスタンスを作らないといけない
  • デリゲートを作るためだけにそれ専用メソッドを記述するのは面倒
  • メソッドの記述位置とそのメソッドを参照するデリゲートの生成・利用位置が離れていると読みずらい
  • デリゲート生成のためだけに使うメソッドを、インラインで記述して、デリゲートを作れたら便利
  • 匿名関数をつかってインラインでデリゲートのインスタンスを生成できる
  • 匿名関数には、匿名メソッドとラムダ式がある

ラムダ式について

 いよいよラムダ式の登場です。ラムダ式は匿名関数の一つです。ですがラムダ式の前に、まずはもう一つの匿名関数である匿名メソッドを紹介します。

 ところで前節を読んで、「デリゲートは『メソッドを参照する型』って言ってたけど、匿名関数によってインラインで書いた処理からデリゲートを作れる?意味がわからないぞ!『メソッドを参照する型』と矛盾しないか?」と思った方もいるかもしれません。一見矛盾しているように見えますが、匿名関数でデリゲートは作れますし、デリゲートはメソッドを参照する型というのも間違っていません。実はコンパイラが頑張ってくれているのです。簡単にではありますが、この節ではラムダ式の裏側も紹介します。

ラムダ式の前に匿名メソッド

 まずは前節のコードをコメントを書き換えて再掲します。コメントも読んでくださいね。

// デリゲートを作るためだけのメソッド、利用箇所から離れているのがbad
public static bool IsLengthLessThan5 (string str)
{
     return str.Length < 5;
}

public static void Main (String[] args)
{
    List<string> names = new List<string>
    {
        "Taro",
        "Jiro",
        "Saburo",
        "Umeko",
        "Takeko",
        "Matsuko",
    };

    // IsLengthLessThan5を参照するFunc<string, bool>型のデリゲートを生成
    // IsLengthLessThan5が離れているので、読む際いったりきたりしないといけない
    // ここのデリゲート生成箇所でIsLengthLessThan5の処理を記述したい
    Func<string, bool> predicate = IsLengthLessThan5;

    int count = CountList(names, predicate);
}

 さて、Func<string, bool>型のpredicateを生成する箇所でインラインで処理を記述できれば便利ですよね。実際のコードではコンパイルエラーになりますが、こんな感じで書けると嬉しいですね。

// デリゲートを作るためだけのメソッドをインラインに
// 先ほどのIsLengthLessThan5メソッドを持ってきただけ
// 利用箇所と実装箇所が近くてGood
// このコードはコンンパイルエラーになる
Func<string, bool> predicate = public static bool IsLengthLessThan5 (string str) {
     return str.Length < 5;
};

 どうでしょうか。こんな風にインラインで処理を記述できたらいいですね。前振りが長くなりましたがやっと匿名関数の一つである匿名メソッドの登場です。上記のコードから、public修飾子、static修飾子、返り値型であるboolを削除します。そしてメソッド名IsLengthLessThan5を削除して代わりにdelegateと記述します。

// これが匿名メソッド!
// 処理の実装箇所とデリゲートの生成の利用箇所が近くてGood
Func<string, bool> predicate = delegate (string str) {
     return str.Length < 5;
};

 上記のコードは、匿名メソッドの一例です。上記のように匿名メソッドを使うことでインラインで処理を記述してデリゲートを生成することが可能です。このようにC# 2.0で登場した匿名メソッドを使えば、デリゲートを作るためだけのメソッドをいちいち定義する必要もなく、読んでいる際もいったりきたりする必要はありません。

 そんな便利な匿名メソッドですがC# 3.0とそれ以降では匿名メソッドを使うべきではありません。今回は理解の促進のために匿名メソッドを説明しましたが、実際にC# 3.0とそれ以降ではラムダ式を使ってくださいね。

 次はいよいよラムダ式の登場です。

ラムダ式の登場

 匿名関数の一つである匿名メソッドによりインラインで処理を記述してデリゲートを作ることが可能になりました。ですが匿名メソッドはラムダ式に比べると冗長です。

 デリゲートの生成はC# 3.0で加わったラムダ式を用いることでより簡潔になります。先ほどの匿名メソッドを用いたデリゲート生成を再掲します。

Func<string, bool> predicate = delegate (string str) {
     return str.Length < 5;
};

 これをラムダ式で書き換えます。

// ラムダ式で匿名メソッドを書き換え
Func<string, bool> predicate = (string str) => {
     return str.Length < 5;
};

 delegateが無くなって、違う位置に=>が挿入されましたね。この記述だとあまり短くなっていませんが、ラムダ式はさらに短くすることが可能です。

// ラムダ式をさらに短く
Func<string, bool> predicate = str => str.Length < 5;

 上記のように短くなっていても、『stringを引数にとってその長さが5未満ならば真(true)、そうでないならば偽(false)を返す処理を参照するデリゲート、ということは変わっていません。(「処理」と書きましたが、実際はメソッドを参照しています。これについては後述します)

 ラムダ式の記法についてや型の推論については、いろいろな方が多く投稿されているので本投稿ではどのように記述できるかはこれ以上触れません。

大切なのは、

  • 匿名関数を使えばインラインでデリゲートの処理を記述し、デリゲートを生成できる
  • 匿名関数の一つはラムダ式
  • もう一つの匿名関数である匿名メソッドよりも簡潔に記述することができる
  • C# 3.0以降であれば匿名メソッド式でなく、ラムダ式を使うべき

ということです。

ラムダ式はデリゲート生成のためだけのものではない

 ラムダ式は匿名関数の一つで、デリゲートを生成することができます。しかしラムダ式の機能はそれだけではありません。もう一つ機能があります。ラムダ式は式ツリー(式木)の生成にも用いることができるのです。

 今回は、「ラムダ式はデリゲートの生成だけのものではない」ということだけ覚えておいてください。

ラムダ式を記述するとどうなるか

 この投稿では「デリゲートはメソッドを参照する型」と述べてきました。そして匿名関数(匿名メソッド式・ラムダ式)を使えば、メソッドを作らなくてもインラインで処理を記述してデリゲートのインスタンスを生成できることを説明しました。

 一見この二つは矛盾しているようにも見えます。匿名関数によりインラインで記述した処理はメソッドではないように見えるからです。

 実際にはこれらは矛盾していません。デリゲートはメソッドを参照する型ですし、匿名関数(匿名メソッド式・ラムダ式)を使ってデリゲートを生成することが可能です。

 それにはコンパイラが関係します。コンパイラは匿名関数を記述された場合、その匿名関数の記述に該当するクラスメソッドまたはインスタンスメソッド、もし必要であれば新たにクラスを生成します。匿名関数によるデリゲートは、これらのコンパイラが生成したメソッドを参照するデリゲートとして生成されるのです。

 具体的にどのようなコードが作られるかは、「++C++;// 未確認飛行 C」の「[雑記] 匿名デリゲートのコンパイル結果」で説明されているので、そちらを参考にしてください。

この節のまとめ

  • 匿名関数の一つが匿名メソッドでC# 2.0で導入された
  • 匿名関数のもう一つがラムダ式でC# 3.0で導入された
  • C# 3.0以降であればラムダ式を使うべき
  • ラムダ式はデリゲートとだけでなく式ツリーの生成にも使う
  • 匿名関数も内部的にはコンパイラがメソッドやクラスを生成して、そのメソッドを参照し、デリゲートを生成している

あれは何がまちがっていたのか?

 本投稿の冒頭で、

  • 引数にラムダ式を渡す
  • ラムダ式の型はFuncとActionが用意されている
  • ラムダ式はデリゲートを作るためのもの

 これらは正確には違ったり、正しくなかったり、ちょっと足りなかったりするの述べましたね。これを説明します。

「引数にラムダ式を渡す」は正確には違う

引数で渡すのはデリゲートですね。ラムダ式で作ったデリゲートを渡すことが非常に多いとは思いますが。

「ラムダ式の型はFuncとActionが用意されている」は正しくない

 ラムダ式で生成できるデリゲートはFunc系とAction系だけではありません。それ以外のデリゲート型もあって、ラムダ式を使ってそれらも当然作れます。またActionという型はありますが、Funcという型はありません。Func<TResult>ならばありますが。ActionとAction<T>は違う型ですし、Func<TResult>とFunc<T, TResult>も違う型です。

 それからラムダ式はデリゲートだけでなく、式ツリーも生成できますね。

「ラムダ式はデリゲートを作るためのもの」はちょっと足りない

 これは間違っているわけではないのですが、ちょっとだけたりませんね。ラムダ式は式ツリーも作れますから。より正確には、

 「ラムダ式はデリゲートと式ツリーを作るためのもの」

 ですね。

LINQにむけて

 匿名関数の節のサンプルを再掲します。

public static bool IsLengthLessThan5 (string str)
{
     return str.Length < 5;
}

public static void Main (String[] args)
{
     List<string> names = new List<string>
     {
          "Taro",
          "Jiro",
          "Saburo",
          "Umeko",
          "Takeko",
          "Matsuko",
     };

     Func<string, bool> predicate = IsLengthLessThan5;

     int count = CountList(names, predicate);
}

 さて上記のコードでは、一度Func<string, bool>型のローカル変数predicateを宣言し、デリゲートのインスタンスを作っています。メソッドの引数としていきなりデリゲートを生成することもできます。該当部分だけ書き換えます。

int count = CountList(names, new Func<string, bool>(IsLengthLessThan5));

 これは次のように記述することも可能です。

int count = CountList(names, IsLengthLessThan5);

 そしてラムダ式を用いて、次のように書くことも可能です。

int count = CountList(names, (string str) => {
    return str.Length < 5;
});

 もっと短くしてこういう風にも書けます。

int count = CountList(names, str => str.Length < 5);

 このように記述できるのであれば、非常に簡潔になりますし読みやくなりますね。

 さてこのCountListメソッド、実はLINQのCountメソッドを参考に改変したものです。今回は説明の簡略化のために自作のCountListメソッドを用いました。

 今回の投稿の範囲外なのですが簡単に説明します。LINQのCountメソッドはIEnumerable<T>型の拡張メソッドになっていて、IEnumerable<T>を実装したクラスであればインスタンスメソッドの書き方でコードを記述できます。第二引数として(見かけ上は第一引数)はFunc<TSource, bool>型を取ります。上記コードと違い実際はnullチェックなどが入っています。

 LINQのCountメソッドの例を示します。

int count = names.Count(str => str.Length < 5);

 ラムダ式とデリゲートとFunc<T, TResult>の次は拡張メソッドと型推論を勉強すれば、LINQの理解はとても進むと思います!

さいごに

 この投稿は私自身がLINQを勉強し始める時に知りたかった情報を、自分が読みたかったようにまとめたものです。大分長くなってしまいました。分かりづらい点などありましたら、ぜひ質問してください。また大変申し訳ありませんが、間違っている点がありましたら指摘していただけると嬉しいです。

 私が初めてLINQの名前を聞いたのは、日本Androidの会Unity部の最初のミーティングだったと思います。「UnityはC#で書けるのがいいね、LINQっていうのが非常に便利なんだ。」そう教えていただいたのを覚えています。その時はLINQというものがあまりわかっていなかったのですが、今ではLINQなしでコードを書くのは非常に苦痛で仕方ありません。

 そんな私がLINQを勉強する上で一番苦労したのはラムダ式です。ラムダ式の書き方を覚えるのに苦労したのではありません。ラムダ式を書いて起こっていることのイメージがつかめなかったのです。メソッドのリファレンスには、Func<TSource, bool> と書いているけれど、num => num > 0とコードで書いている。これでなぜいいのか、裏で何が起きているのかを理解するのに苦労しました。

 ラムダ式を調べるのではなく、まずデリゲートを理解することが大事たったというこよに気づくまで、私は大分時間がかかってしまいました。

 岩永さんの、「++C++; // 未確認飛行C」のラムダ式は読んでいたのですが、今になって思うと デリゲートをなぜもっと早くしっかり読まなかったのかと後悔しています。また、他の方の投稿の「デリゲート~ラムダ式の歴史を辿って」という投稿のおかげでも理解が深まりました。普段は使わない古いC#の書き方や古いC#ではできなかったことを知るのが、非常に理解の助けになりました。

 この投稿が読者の方のC#の、そしてLINQの学習の助けになると嬉しいです。

関連・参考

MSDN

++C++; // 未確認飛行C

もりひろゆきの日々是勉強

書籍