ラムダ式 (C# プログラミング ガイド)
ラムダ式は、デリゲートまたは式ツリー型を作成するために使用できる匿名関数です。ラムダ式を使用して、引数として渡された場合、または関数呼び出しの結果値として返すことができるローカル関数を記述できます。ラムダ式は、LINQクエリ式を記述する場合に特に便利です。
ラムダ式を作成するには、ラムダ演算子の左側に =>入力パラメーター (ある場合) を指定し、反対側に式またはステートメントのブロックを設定します。たとえば、ラムダ式 x => x * x は x という名前のを指定し、x の値を2乗したパラメーターを返します。次の例に示すように、デリゲート型にこの式を割り当てることができます:
delegate int del(int i);
static void Main(string[] args)
{
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
}
式ツリー型を作成するには
using System.Linq.Expressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Expression<del> myET = x => x * x;
}
}
}
=> 演算子と代入 (=) は優先順位が同じで、結合規則が右から左です。
ラムダは、LINQ のメソッド ベースのクエリ内で標準クエリ演算子のメソッド (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 は最大 16 の入力パラメーターを持つ Func デリゲートです。ラムダ式は、こうした式ツリーを構築するための非常に簡潔な方法でもあります。ラムダを使用すると Where 呼び出しの外観を似たものにできますが、ラムダから実際に作成されるオブジェクトの型は異なります。
先ほどの例では、デリゲート シグネチャは暗黙的に型指定される int 型の入力パラメーターを 1 つ持ち、int を返します。このラムダ式を同じ型のデリゲートに変換することができます。デリゲートも 1 つの入力パラメーター (x) を持ち、コンパイラが暗黙的に int 型に変換できる値を返すからです (型の推論については後のセクションで詳しく説明します)。入力パラメーターとして 5 を使用してデリゲートを呼び出すと、デリゲートは 25 という結果を返します。
is 演算子または as 演算子の左辺にラムダを使用することはできません。
匿名メソッドに適用される制限は、すべてラムダ式にも適用されます。詳細については、「匿名メソッド (C# プログラミング ガイド)」を参照してください。
式形式のラムダ
右辺に式があるラムダ式を式形式のラムダと呼びます。式形式のラムダは、式ツリー (C# および Visual Basic)の構築に幅広く使用されます。式形式のラムダは式の結果を返します。基本的な形式は次のとおりです。
(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");
匿名メソッドと同様、ステートメント形式のラムダを使用して式ツリーを作成することはできません。
単一のラムダ
簡単に async と [await] のキーワードを使用して非同期操作するステートメントを組み込むとラムダ式を作成できます。たとえば、次のWindowsフォームには、非同期のメソッドを呼び出し、待機するイベント ハンドラーが含まれています ExampleMethodAsync。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
asyncのラムダを使用して、同じイベント ハンドラーを追加できます。次の例に示すように、このハンドラーを追加するには、ラムダのパラメーター リストの前に async 修飾子を追加します。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
};
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
非同期のメソッドを作成および使用する方法の詳細については、Async および Await を使用した非同期プログラミング (C# および Visual Basic)を参照してください。
標準クエリ演算子でのラムダ
標準クエリ演算子の多くが、汎用デリゲートの 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の左側にある要素をすべて含むシーケンスを作成します:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
次の例は、複数の入力パラメーターをかっこで囲んで指定する方法を示しています。このメソッドは、値がその位置よりも小さい数値が出現するまで配列 numbers に含まれるすべての要素を返します。ラムダ演算子 (=>) と以上演算子 (>=) を混同しないようにしてください。
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
ラムダにおける型の推論
ラムダを記述する際、多くの場合は入力パラメーターの型を指定する必要はありません。これは、ラムダ本体や基になるデリゲート型など C# 言語仕様に記述されている要素に基づいて、コンパイラが型を推論できるためです。ほとんどの標準クエリ演算子では、最初の入力がソース シーケンス内の要素の型です。したがって、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# 言語仕様」を参照してください。言語仕様は、C# の構文と使用法に関する信頼性のある情報源です。
参考書籍の該当する章
『C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers』の「Delegates, Events, and Lambda Expressions」