次の方法で共有


ラムダ式と匿名関数

"ラムダ式" を使用して匿名関数を作成します。 ラムダ宣言演算子=>を使用して、ラムダのパラメーター リストを式本体から分離します。 ラムダ式は、次の 2 つの形式のいずれかにすることができます。

ラムダ式を作成するには、ラムダ演算子の左辺に入力パラメーターを指定し (ある場合)、右辺に式またはステートメント ブロックを指定します。

ラムダ式は、デリゲート型に変換できます。 パラメータと戻り値の型は、ラムダ式を変換できるデリゲート型を定義します。 ラムダ式が値を返さない場合は Action デリゲート型のいずれかに変換でき、値を返す場合はFunc デリゲート型のいずれかに変換できます。 たとえば、2 つのパラメーターがあり、値を返さないラムダ式は、Action<T1,T2> デリゲートに変換できます。 1 つのパラメーターがあり、値を返すラムダ式は、Func<T,TResult> デリゲートに変換できます。 次の例では、x という名前のパラメーターを指定し、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

メソッド ベースの構文を使用して System.Linq.Enumerable クラス (たとえば、LINQ to Objects、LINQ to XML など) の Enumerable.Select メソッドを呼び出すと、パラメーターはデリゲート型 System.Func<T,TResult> になります。 System.Linq.Queryable クラス (たとえば、LINQ to SQL など) の Queryable.Select メソッドを呼び出すと、パラメーター型は式ツリー型 Expression<Func<TSource,TResult>> になります。 どちらの場合も、同じラムダ式を使用してパラメーター値を指定できます。 これにより、2 つの Select 呼び出しは外観が似ていますが、ラムダから実際に作成されるオブジェクトの型は異なります。

式形式のラムダ

=> 演算子の右辺に式があるラムダ式を "式形式のラムダ" と呼びます。 式形式のラムダは式の結果を返します。基本的な形式は次のとおりです。

(input-parameters) => expression

式形式のラムダの本体を、メソッド呼び出しで構成できます。 ただし、SQL Server などの .NET 共通言語ランタイム (CLR) のコンテキスト外部で評価される式ツリーを作成する場合は、ラムダ式内でメソッド呼び出しを使用しないでください。 .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!

ステートメント形式のラムダを使用して式ツリーを作成することはできません。

ラムダ式の入力パラメーター

ラムダ式の入力パラメーターをかっこで囲みます。 入力パラメーターがないことを指定するには、次のように空のかっこを使用します。

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;

ラムダ ディスカード パラメーターは、ラムダ式を使用してイベント ハンドラー指定する場合に便利です。

Note

下位互換性のために、1 つの入力パラメーターにのみ _ という名前が付けられた場合、ラムダ式内で _ はそのパラメーターの名前として扱われます。

C# 12 以降では、ラムダ式のパラメーターに "既定値" を提供できます。 既定のパラメーター値の構文と制約は、メソッドとローカル関数の場合と同じです。 次の例では、既定のパラメーターでラムダ式を宣言し、その後それを既定値で 1回、2 つの明示的パラメーターで 1 回呼び出します。

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)

通常、タプルのフィールド名は Item1Item2 などのようになります。 ただし、次の例のとおり、名前付きのコンポーネントを持つタプルを定義することができます。

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> ファミリに属する型の入力パラメーターを持ちます。 これらのデリゲートは、型パラメーターを使用して、入力パラメーターの数と型に加え、デリゲートの戻り値の型を定義します。 Func デリゲートは、ソース データのセット内の各要素に適用されるユーザー定義の式をカプセル化する場合に便利です。 たとえば、Func<T,TResult>デリゲート型を考えてみましょう。

public delegate TResult Func<in T, out TResult>(T arg)

このデリゲートを Func<int, bool> としてインスタンス化できます。 int は入力パラメーター、bool は戻り値です。 戻り値は必ず最後の型パラメーターで指定されます。 たとえば、Func<int, string, bool> は 2 つの入力パラメーター ( intstring) と戻り値の型 bool を持つデリゲートを定義しています。 次の Func デリゲートを呼び出すと、入力パラメーターが 5 に等しいかどうかを示す true または false が返されます。

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

たとえば、Queryable 型で定義された標準クエリ演算子において、引数型が Expression<TDelegate> の場合もラムダ式を使用できます。 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) をカウントします。

次の例では、numbers 配列内で 9 より前にある要素をすべて含むシーケンスを作成します。これは、9 がシーケンス内で条件を満たさない最初の数値であるためです。

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 型を指します。

C# 10 以降、ラムダ式は "自然型" を持つことができます。 ラムダ式に対して Func<...>Action<...> などのデリゲート型を強制的に宣言するのではなく、コンパイラにより、ラムダ式からデリゲート型を推論することができます。 たとえば、次のような宣言があるとします。

var parse = (string s) => int.Parse(s);

コンパイラにより、parseFunc<string, int> と推論することができます。 適切なものが存在する場合、コンパイラには Func または Action のデリゲートが選択されます。 それ以外の場合は、デリゲート型が合成されます。 たとえば、ラムダ式のパラメーターが ref の場合、デリゲート型が合成されます。 ラムダ式が自然型の場合、System.ObjectSystem.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

C# 10 以降、ラムダ式の戻り値の型を入力パラメーターの前に指定できるようになりました。 明示的な戻り値の型を指定する場合は、入力パラメーターをかっこで囲む必要があります。

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

属性

C# 10 より、ラムダ式とそのパラメーターに属性を追加できます。 次の例は、ラムダ式に属性を追加する方法を示しています。

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
}

ラムダ式における変数のスコープには、次の規則が適用されます。

  • 取り込まれた変数は、その変数を参照するデリゲートがガベージ コレクションの対象になるまでガベージ コレクトされません。
  • ラムダ式内に導入された変数は、外側のメソッドでは参照できません。
  • ラムダ式は、外側のメソッドから inref、または out パラメーターを直接取り込むことはできません。
  • ラムダ式に含まれる return ステートメントで外側のメソッドが戻ることはありません。
  • ジャンプ先がラムダ式ブロックの外側にある場合は、ラムダ式に gotobreak、または continue ステートメントを含めることはできません。 また、ジャンプ先がブロックの内部にある場合に、ラムダ式ブロックの外部でジャンプ ステートメントを使用するとエラーになります。

ラムダ式に static 修飾子を適用して、ラムダによる意図しないローカル変数またはインスタンスの状態のキャプチャを防ぐことができます。

Func<double, double> square = static x => x * x;

静的ラムダでは外側のスコープからローカル変数またはインスタンスの状態をキャプチャすることはできませんが、静的メンバーと定数の定義は参照することができます。

C# 言語仕様

詳細については、「C# 言語仕様」の無名関数の式に関するセクションを参照してください。

これらの機能の詳細については、機能の提案に関する次の記述を参照してください。

関連項目