ラムダ式 (C# プログラミング ガイド)
更新 : 2007 年 11 月
ラムダ式は式とステートメントを含めることができる匿名関数であり、デリゲート型または式ツリー型を作成するために使用できます。
すべてのラムダ式でラムダ演算子 => が使用され、この演算子を「~に入力」と読みます。ラムダ演算子の左辺で入力パラメータを指定し (ある場合)、右辺には式ブロックまたはステートメント ブロックが入ります。ラムダ式 x => x * x は、「x を x に x 回入力」と読みます。この式は、次のようにデリゲート型に割り当てることができます。
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
式ツリー型を作成するには
using System.Linq.Expressions;
// ...
Expression<del> = x => x * x;
=> 演算子と代入 (=) は優先順位が同じで、結合規則が右から左です。
ラムダは、LINQ のメソッド ベースのクエリ内で標準クエリ演算子のメソッド (Where や Where など) の引数として使用されます。
メソッド ベースの構文を使用して (LINQ to Objects および LINQ to XML の場合と同様に) Enumerable クラスの Where メソッドを呼び出すと、パラメータはデリゲート型 System.Func<T, TResult> になります。ラムダ式はデリゲートを作成するための最も便利な方法です。たとえば (LINQ to SQL の場合と同様に) System.Linq.Queryable クラスの同じメソッドを呼び出すと、パラメータ型は System.Linq.Expressions.Expression<Func> になります。Func は最大 5 つの入力パラメータを持つ Func デリゲートです。ラムダ式は、こうした式ツリーを構築するための非常に簡潔な方法でもあります。ラムダを使用すると Where 呼び出しの外観を似たものにできますが、ラムダから実際に作成されるオブジェクトの型は異なります。
先ほどの例では、デリゲート シグネチャは暗黙的に型指定される int 型の入力パラメータを 1 つ持ち、int を返します。このラムダ式を同じ型のデリゲートに変換することができます。デリゲートも 1 つの入力パラメータ (x) を持ち、コンパイラが暗黙的に int 型に変換できる値を返すからです (型の推論については後のセクションで詳しく説明します)。入力パラメータとして 5 を使用してデリゲートを呼び出すと、デリゲートは 25 という結果を返します。
is 演算子または as 演算子の左辺にラムダを使用することはできません。
匿名メソッドに適用される制限は、すべてラムダ式にも適用されます。詳細については、「匿名メソッド (C# プログラミング ガイド)」を参照してください。
式形式のラムダ
右辺に式があるラムダ式を式形式のラムダと呼びます。式形式のラムダは、式ツリーの構築に幅広く使用されます。式形式のラムダは式の結果を返します。基本的な形式は次のとおりです。
(input parameters) => expression
かっこはラムダの入力パラメータが 1 つの場合のみ省略可能で、それ以外の場合は必須です。入力パラメータが 2 つ以上ある場合は、かっこで囲んで各パラメータをコンマで区切ります。
(x, y) => x == y
コンパイラが入力の型を推論するのが困難または不可能な場合もあります。このような場合は、次の例のように型を明示的に指定できます。
(int x, string s) => s.Length > x
入力パラメータがないことを指定するには、次のように空のかっこを使用します。
() => SomeMethod()
この例では、式形式のラムダの本体をメソッド呼び出しで構成できることに注目してください。ただし、SQL Server などの別のドメインで処理される式ツリーを作成する場合は、ラムダ式にメソッド呼び出しを使用することはできません。.NET 共通言語ランタイムのコンテキストの外部では、これらのメソッドは通用しません。
ステートメント形式のラムダ
ステートメント形式のラムダは式形式のラムダに似ていますが、ステートメントが中かっこで囲まれる点が異なります。
(input parameters) => {statement;}
ステートメント形式のラムダの本体は任意の数のステートメントで構成できますが、実際面では通常、2、3 個以下にします。
delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");
匿名メソッドと同様、ステートメント形式のラムダを使用して式ツリーを作成することはできません。
標準クエリ演算子でのラムダ
標準クエリ演算子の多くが、汎用デリゲートの Func<T, TResult> ファミリに属する型の入力パラメータを持ちます。Func<T, TResult> デリゲートは型パラメータを使用して入力パラメータの数と型、およびデリゲートの戻り値の型を定義します。Func デリゲートは、ソース データのセット内の各要素に適用されるユーザー定義の式をカプセル化する場合に非常に便利です。たとえば、次のデリゲート型を考えてみましょう。
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
このデリゲートを Func<int,bool> myFunc としてインスタンス化できます。int は入力パラメータ、bool は戻り値です。戻り値は必ず最後の型パラメータで指定されます。Func<int, string, bool> は 2 つの入力パラメータ (int と string) と戻り値の型 bool を持つデリゲートを定義しています。次の Func デリゲートを呼び出すと、入力パラメータが 5 に等しいかどうかを示す true または false が返されます。
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course
たとえば System.Linq.Queryable で定義された標準クエリ演算子において、引数型が Expression<Func> の場合もラムダ式を使用できます。Expression<Func> 引数を指定すると、ラムダは式ツリーにコンパイルされます。
標準クエリ演算子である Count メソッドを次に示します。
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
入力パラメータの型はコンパイラが推論できますが、明示的に指定することもできます。この特定のラムダ式は、2 で除算したときに剰余が 1 になる整数 (n) をカウントします。
次のメソッドは、配列 numbers に含まれる要素のうち "9" の前に現れるものをすべて含むシーケンスを作成します。これは、"9" がシーケンス内で条件を満たさない最初の数値だからです。
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
次の例は、複数の入力パラメータをかっこで囲んで指定する方法を示しています。このメソッドは、値がその位置よりも小さい数値が出現するまで配列 numbers に含まれるすべての要素を返します。ラムダ演算子 (=>) と以上演算子 (>=) を混同しないようにしてください。
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
ラムダにおける型の推論
ラムダを記述する際、多くの場合は入力パラメータの型を指定する必要はありません。ラムダ本体や基になるデリゲート型など C# 3.0 言語仕様に記述されている要素に基づいて、コンパイラが型を推論できるからです。ほとんどの標準クエリ演算子では、最初の入力がソース シーケンス内の要素の型です。したがって、IEnumerable<Customer> を問い合わせると、入力変数は Customer オブジェクトであると推論されます。これは、そのメソッドとプロパティにアクセスできることを意味します。
customers.Where(c => c.City == "London");
ラムダの一般規則は、次のとおりです。
ラムダにはデリゲート型と同じ数のパラメータが含まれていなければなりません。
ラムダに含まれる各入力パラメータは、対応するデリゲート パラメータに暗黙的に変換できなければなりません。
ラムダの戻り値 (ある場合) は、デリゲートの戻り値の型に暗黙的に変換できなければなりません。
共通型システムには "ラムダ式" の概念が組み込まれていないため、ラムダ式自体は型を持ちません。しかし、変則的ではあってもラムダ式の "型" を表現できると都合が良い場合もあります。このような場合の型は、ラムダ式の変換後のデリゲート型または Expression 型を指します。
ラムダ式における変数のスコープ
ラムダは、ラムダの定義が収容されている外側のメソッドまたは型のスコープ内の外部変数を参照できます。こうして取り込まれた変数は、ラムダ式で使用するために格納されます。これは、変数がスコープ外に出てガベージ コレクトされる場合でも変わりません。外部変数は、ラムダ式で使用される前に明示的に代入する必要があります。次の例は、こうした規則を示しています。
delegate bool D();
delegate bool D2(int i);
class Test
{
D del;
D2 del2;
public void TestMethod(int input)
{
int j = 0;
// Initialize the delegates with lambda expressions.
// Note access to 2 outer variables.
// del will be invoked within this method.
del = () => { j = 10; return j > input; };
// del2 will be invoked after TestMethod goes out of scope.
del2 = (x) => {return x == j; };
// Demonstrate value of j:
// Output: j = 0
// The delegate has not been invoked yet.
Console.WriteLine("j = {0}", j);
// Invoke the delegate.
bool boolResult = del();
// Output: j = 10 b = True
Console.WriteLine("j = {0}. b = {1}", j, boolResult);
}
static void Main()
{
Test test = new Test();
test.TestMethod(5);
// Prove that del2 still has a copy of
// local variable j from TestMethod.
bool result = test.del2(10);
// Output: True
Console.WriteLine(result);
Console.ReadKey();
}
}
ラムダ式における変数のスコープには、次の規則が適用されます。
取り込まれた変数は、それを参照するデリゲートがスコープ外に出るまでガベージ コレクトされません。
ラムダ式内に導入された変数は、外側のメソッドでは参照できません。
ラムダ式は、外側のメソッドの ref パラメータまたは out パラメータを直接取り込むことはできません。
ラムダ式に含まれる return ステートメントで外側のメソッドを戻すことはありません。
ラムダ式は、その本体の外部または含まれている匿名関数本体内をジャンプ先とする goto ステートメント、break ステートメント、continue ステートメントを含むことはできません。
C# 言語仕様
詳細については、「C# 言語仕様」の次のセクションを参照してください。
- 5.3.3.29 匿名関数