ラムダ式と匿名関数
匿名関数を作成するには、ラムダ式 を使用します。 ラムダ宣言演算子 =>
を使用して、ラムダのパラメーター リストを本体から分離します。 ラムダ式には、次の 2 つの形式のいずれかを指定できます。
式形式のラムダでは、式本体に式が含まれます。
(input-parameters) => expression
ステートメント形式のラムダでは、式本体にステートメント ブロックが含まれます。
(input-parameters) => { <sequence-of-statements> }
ラムダ式を作成するには、ラムダ演算子の左側に入力パラメーター (存在する場合) を指定し、もう一方の側に式またはステートメント ブロックを指定します。
任意のラムダ式を デリゲート 型に変換できます。 そのパラメーターと戻り値の型は、ラムダ式を変換できるデリゲート型を定義します。 ラムダ式が値を返さない場合は、Action
デリゲート型のいずれかに変換できます。それ以外の場合は、Func
デリゲート型のいずれかに変換できます。 たとえば、2 つのパラメーターを持ち、値を返さないラムダ式は、Action<T1,T2> デリゲートに変換できます。 1 つのパラメーターを持ち、値を返すラムダ式は、Func<T,TResult> デリゲートに変換できます。 次の例では、x
という名前のパラメーターを指定し、2 乗の値 x
返すラムダ式 x => x * x
がデリゲート型の変数に割り当てられます。
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25
式ラムダは、次の例に示すように、式ツリー 型に変換することもできます。
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)
デリゲート型または式ツリーのインスタンスを必要とするコードでは、ラムダ式を使用します。 1 つの例として、バックグラウンドで実行する必要があるコードを渡す Task.Run(Action) メソッドの引数があります。 次の例に示すように、C#で LINQ
int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
メソッド ベースの構文を使用して、LINQ to Objects や LINQ to XML など、System.Linq.Enumerable クラスの Enumerable.Select メソッドを呼び出す場合、パラメーターは System.Func<T,TResult>デリゲート型です。 LINQ to SQL など、System.Linq.Queryable クラスで Queryable.Select メソッドを呼び出すと、パラメーター型は式ツリー型 Expression<Func<TSource,TResult>>
。 どちらの場合も、同じラムダ式を使用してパラメーター値を指定できます。 これにより、2 つの Select
呼び出しは似ていますが、実際にはラムダから作成されたオブジェクトの種類が異なります。
式形式のラムダ
=>
演算子の右側に式を持つラムダ式は、式ラムダと呼ばれます。 式ラムダは、式の結果を返し、次の基本的な形式をとります。
(input-parameters) => expression
式ラムダの本体は、メソッド呼び出しで構成できます。 ただし、SQL Server など、.NET 共通言語ランタイム (CLR) のコンテキスト外で評価される
ステートメント形式のラムダ
ステートメント形式のラムダは式形式のラムダに似ていますが、ステートメントが中かっこで囲まれる点が異なります。
(input-parameters) => { <sequence-of-statements> }
ステートメントラムダの本体は、任意の数のステートメントで構成できます。ただし、実際には、通常は 2 つまたは 3 つ以下です。
Action<string> greet = name =>
{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
ステートメント ラムダを使用して式ツリーを作成することはできません。
ラムダ式の入力パラメーター
ラムダ式の入力パラメーターはかっこで囲みます。 空の丸括弧を使って、入力パラメーターを 0 個指定します。
Action line = () => Console.WriteLine();
ラムダ式に入力パラメーターが 1 つしかない場合、かっこは省略可能です。
Func<double, double> cube = x => x * x * x;
2 つ以上の入力パラメーターはコンマで区切られます。
Func<int, int, bool> testForEquality = (x, y) => x == y;
コンパイラが入力パラメーターの型を推測できない場合があります。 次の例に示すように、型を明示的に指定できます。
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
入力パラメーターの型は、すべて明示的または暗黙的である必要があります。それ以外の場合は、CS0748 コンパイラ エラーが発生します。
破棄を使用すると、式で使用しないラムダ式の 2 つ以上の入力パラメーターを指定できます。
Func<int, int, int> constant = (_, _) => 42;
ラムダの破棄パラメーターは、ラムダ式を使用してイベント ハンドラー指定する場合に役立つことがあります。
手記
下位互換性のために、_
という名前の入力パラメーターが 1 つだけの場合、ラムダ式内では、_
はそのパラメーターの名前として扱われます。
C# 12 以降では、ラムダ式のパラメーター
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
params
配列またはコレクションをパラメーターとして使用してラムダ式を宣言することもできます。
var sum = (params IEnumerable<int> values) =>
{
int sum = 0;
foreach (var value in values)
sum += value;
return sum;
};
var empty = sum();
Console.WriteLine(empty); // 0
var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15
これらの更新の一環として、既定のパラメーターを持つメソッド グループがラムダ式に割り当てられると、そのラムダ式にも同じ既定のパラメーターが割り当てられます。 params
コレクション パラメーターを持つメソッド グループをラムダ式に割り当てることもできます。
パラメーターとして既定のパラメーターまたは params
コレクションを持つラムダ式には、Func<>
型または Action<>
型に対応する自然な型がありません。 ただし、既定のパラメーター値を含むデリゲート型を定義できます。
delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);
または、暗黙的に型指定された変数を var
宣言と共に使用して、デリゲート型を定義できます。 コンパイラは、適切なデリゲート型を合成します。
ラムダ式の既定のパラメーターの詳細については、ラムダ式の既定のパラメーター
非同期ラムダ
async と await キーワードを使用して、非同期処理を組み込むラムダ式やステートメントを簡単に作成できます。 たとえば、次の Windows フォームの例には、非同期メソッド ExampleMethodAsync
を呼び出して待機するイベント ハンドラーが含まれています。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
}
private async void button1_Click(object sender, EventArgs e)
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
非同期ラムダを使用して、同じイベント ハンドラーを追加できます。 次の例に示すように、このハンドラーを追加するには、ラムダ パラメーター リストの前に async
修飾子を追加します。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
};
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
非同期メソッドを作成して使用する方法の詳細については、「async および awaitを使用した非同期プログラミングの
ラムダ式とタプル
C# 言語では、タプルの組み込みサポートが提供されます。 ラムダ式の引数としてタプルを指定できます。また、ラムダ式からタプルを返すこともできます。 場合によっては、C# コンパイラは型推論を使用してタプル コンポーネントの型を決定します。
タプルは、そのコンポーネントのコンマ区切りのリストをかっこで囲んで定義します。 次の例では、3 つのコンポーネントを持つタプルを使用して、数値のシーケンスをラムダ式に渡します。この式は、各値を 2 倍にし、乗算の結果を含む 3 つのコンポーネントを持つタプルを返します。
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)
通常、タプルのフィールドの名前は Item1
、Item2
などです。 ただし、次の例のように、名前付きコンポーネントを持つタプルを定義できます。
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
C# タプルの詳細については、「タプル型の」を参照してください。
標準クエリ演算子を使用したラムダ
LINQ to Objects は、他の実装の中でも、ジェネリック デリゲートの Func<TResult> ファミリの 1 つである入力パラメーターを持ちます。 これらのデリゲートは、型パラメーターを使用して、入力パラメーターの数と型、およびデリゲートの戻り値の型を定義します。 Func
デリゲートは、一連のソース データ内の各要素に適用されるユーザー定義式をカプセル化するのに役立ちます。 たとえば、Func<T,TResult> デリゲート型について考えてみましょう。
public delegate TResult Func<in T, out TResult>(T arg)
デリゲートは、int
が入力パラメーターであり、bool
が戻り値である Func<int, bool>
インスタンスとしてインスタンス化できます。 戻り値は、常に最後の型パラメーターで指定されます。 たとえば、Func<int, string, bool>
は、int
と string
の 2 つの入力パラメーターを持つデリゲートと、bool
の戻り値の型を定義します。 次の Func
デリゲートは、呼び出されると、入力パラメーターが 5 に等しいかどうかを示すブール値を返します。
Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result); // False
引数の型が Expression<TDelegate>の場合 (たとえば、Queryable 型で定義されている標準クエリ演算子) にラムダ式を指定することもできます。 Expression<TDelegate> 引数を指定すると、ラムダが式ツリーにコンパイルされます。
次の例では、Count 標準クエリ演算子を使用します。
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
コンパイラは、入力パラメーターの型を推論することも、明示的に指定することもできます。 この特定のラムダ式は、2 で除算した場合に 1 の剰余を持つ整数 (n
) をカウントします。
次の例では、9 より前の numbers
配列内のすべての要素を含むシーケンスを生成します。これは、条件を満たしていないシーケンスの最初の数値であるためです。
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3
次の例では、複数の入力パラメーターをかっこで囲んで指定します。 このメソッドは、値が配列内の序数位置より小さい数値を見つけるまで、numbers
配列内のすべての要素を返します。
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4
var numberSets = new List<int[]>
{
new[] { 1, 2, 3, 4, 5 },
new[] { 0, 0, 0 },
new[] { 9, 8 },
new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};
var setsWithManyPositives =
from numberSet in numberSets
where numberSet.Count(n => n > 0) > 3
select numberSet;
foreach (var numberSet in setsWithManyPositives)
{
Console.WriteLine(string.Join(" ", numberSet));
}
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0
ラムダ式での型推論
ラムダを記述する場合、多くの場合、入力パラメーターの型を指定する必要はありません。コンパイラは、C# 言語仕様で説明されているように、ラムダ本体、パラメーター型、およびその他の要因に基づいて型を推論できるためです。 ほとんどの標準クエリ演算子では、最初の入力はソース シーケンス内の要素の型です。 IEnumerable<Customer>
のクエリを実行している場合、入力変数は Customer
オブジェクトであると推論されます。つまり、そのメソッドとプロパティにアクセスできます。
customers.Where(c => c.City == "London");
ラムダの型推論の一般的な規則は次のとおりです。
- ラムダには、デリゲート型と同じ数のパラメーターが含まれている必要があります。
- ラムダの各入力パラメーターは、対応するデリゲート パラメーターに暗黙的に変換できる必要があります。
- ラムダの戻り値 (存在する場合) は、デリゲートの戻り値の型に暗黙的に変換できる必要があります。
ラムダ式の自然型
共通型システムには "ラムダ式" という組み込みの概念がないため、ラムダ式自体には型がありません。ただし、ラムダ式の "型" を非公式に話すと便利な場合があります。 この非公式の "型" は、ラムダ式が変換されるデリゲート型または Expression 型を指します。
ラムダ式には、自然型のを指定できます。 ラムダ式の Func<...>
や Action<...>
などのデリゲート型を強制的に宣言する代わりに、コンパイラはラムダ式からデリゲート型を推論できます。 たとえば、次の宣言を考えてみましょう。
var parse = (string s) => int.Parse(s);
コンパイラは、parse
が Func<string, int>
であると推論できます。 コンパイラは、使用可能な Func
を選択するか、適切なデリゲートが存在する場合は Action
デリゲートを選択します。 それ以外の場合は、デリゲート型を合成します。 たとえば、ラムダ式に ref
パラメーターがある場合、デリゲート型が合成されます。 ラムダ式に自然な型がある場合は、System.Object や System.Delegateなど、明示的でない型に割り当てることができます。
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
厳密に 1 つのオーバーロードを持つメソッド グループ (つまり、パラメーター リストのないメソッド名) には、自然な型があります。
var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose
ラムダ式を System.Linq.Expressions.LambdaExpression(System.Linq.Expressions.Expression) に割り当て、ラムダに自然なデリゲート型がある場合、式には自然型の System.Linq.Expressions.Expression<TDelegate>があり、型パラメーターの引数として自然デリゲート型が使用されます。
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
すべてのラムダ式に自然な型があるわけではありません。 次の宣言について考えてみましょう。
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
コンパイラは、s
のパラメーター型を推論できません。 コンパイラが自然型を推論できない場合は、型を宣言する必要があります。
Func<string, int> parse = s => int.Parse(s);
明示的な戻り値の型
通常、ラムダ式の戻り値の型は明白であり、推論されます。 一部の表現がうまくいかない場合:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
入力パラメーターの前にラムダ式の戻り値の型を指定できます。 明示的な戻り値の型を指定する場合は、入力パラメーターをかっこで指定する必要があります。
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
属性
ラムダ式とそのパラメーターに属性を追加できます。 次の例は、ラムダ式に属性を追加する方法を示しています。
Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;
次の例に示すように、入力パラメーターまたは戻り値に属性を追加することもできます。
var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;
前の例に示すように、ラムダ式またはそのパラメーターに属性を追加するときは、入力パラメーターをかっこで置く必要があります。
重要
ラムダ式は、基になるデリゲート型を介して呼び出されます。 これは、メソッドやローカル関数とは異なります。 デリゲートの Invoke
メソッドは、ラムダ式の属性をチェックしません。 ラムダ式が呼び出されたとき、属性には影響しません。 ラムダ式の属性はコード分析に役立ち、リフレクションを使用して検出できます。 この決定の結果の 1 つは、System.Diagnostics.ConditionalAttribute をラムダ式に適用できないことです。
ラムダ式での外部変数と変数スコープのキャプチャ
ラムダは、外部変数
public static class VariableScopeWithLambdas
{
public class VariableCaptureGame
{
internal Action<int>? updateCapturedLocalVariable;
internal Func<int, bool>? isEqualToCapturedLocalVariable;
public void Run(int input)
{
int j = 0;
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
Console.WriteLine($"Local variable before lambda invocation: {j}");
updateCapturedLocalVariable(10);
Console.WriteLine($"Local variable after lambda invocation: {j}");
}
}
public static void Main()
{
var game = new VariableCaptureGame();
int gameInput = 5;
game.Run(gameInput);
int jTry = 10;
bool result = game.isEqualToCapturedLocalVariable!(jTry);
Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");
int anotherJ = 3;
game.updateCapturedLocalVariable!(anotherJ);
bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
}
// Output:
// Local variable before lambda invocation: 0
// 10 is greater than 5: True
// Local variable after lambda invocation: 10
// Captured local variable is equal to 10: True
// 3 is greater than 5: False
// Another lambda observes a new value of captured variable: True
}
ラムダ式の変数スコープには、次の規則が適用されます。
- キャプチャされた変数は、それを参照するデリゲートがガベージ コレクションの対象になるまでガベージ コレクションされません。
- ラムダ式内で導入された変数は、外側のメソッドでは表示されません。
- ラムダ式は、外側のメソッドから in、ref、または out パラメーターを直接取り込むことはできません。
- ラムダ式に含まれる return ステートメントで外側のメソッドが戻ることはありません。
- ラムダ式には、ジャンプ ステートメントのターゲットがラムダ式ブロックの外にある場合、goto、break、または continue ステートメントを含めることはできません。 ターゲットがブロック内にある場合は、ラムダ式ブロックの外側にジャンプ ステートメントがあるとエラーになります。
ラムダ式に static
修飾子を適用すると、ラムダによってローカル変数またはインスタンスの状態が意図せずキャプチャされるのを防ぐことができます。
Func<double, double> square = static x => x * x;
静的ラムダは、外側のスコープからローカル変数またはインスタンスの状態をキャプチャすることはできませんが、静的メンバーと定数定義を参照できます。
C# 言語仕様
詳細については、C# 言語仕様の 匿名関数式 セクションを参照してください。
これらの機能の詳細については、次の機能提案ノートを参照してください。
関連項目
.NET