Espressioni lambda (informazioni di riferimento per C#)

Si usa un'espressione lambda per creare una funzione anonima. Usare l'operatore di dichiarazione lambda => per separare l'elenco di parametri dell'espressione lambda dal corpo. Un'espressione lambda può essere di una delle due forme seguenti:

Per creare un'espressione lambda, è necessario specificare gli eventuali parametri di input a sinistra dell'operatore lambda e un'espressione o un blocco di istruzioni sull'altro lato.

Qualsiasi espressione lambda può essere convertita in tipo delegato. Il tipo delegato in cui è possibile convertire un'espressione lambda è definito dai tipi dei relativi parametri e del valore restituito. Se un'espressione lambda non restituisce alcun valore, può essere convertita in uno dei tipi delegati Action, altrimenti può essere convertita in uno dei tipi delegati Func. Ad esempio, un'espressione lambda che include due parametri e non restituisce alcun valore può essere convertita in delegato Action<T1,T2>. Un'espressione lambda che include un parametro e restituisce un valore può essere convertita in delegato Func<T,TResult>. Nell'esempio seguente, l'espressione x => x * xlambda , che specifica un parametro denominato x e restituisce il valore quadrato x , viene assegnato a una variabile di un tipo delegato:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

Le espressioni lambda possono anche essere convertite nei tipi di albero delle espressioni , come illustrato nell'esempio seguente:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

È possibile usare espressioni lambda in qualsiasi codice che richiede istanze di tipi delegati o alberi delle espressioni, ad esempio come argomento per il metodo Task.Run(Action) per passare il codice da eseguire in background. È anche possibile usare espressioni lambda quando si scrive LINQ in C#, come illustrato nell'esempio seguente:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

Quando si usa la sintassi basata su metodo per chiamare il metodo Enumerable.Select nella classe System.Linq.Enumerable, ad esempio in LINQ to Objects e LINQ to XML, il parametro è un tipo delegato System.Func<T,TResult>. Quando si chiama il metodo Queryable.Select nella classe System.Linq.Queryable, ad esempio in LINQ to SQL, il tipo di parametro è un tipo albero delle espressioni Expression<Func<TSource,TResult>>. In entrambi i casi è possibile usare la stessa espressione lambda per specificare il valore del parametro. Questo approccio fa sì che le due chiamate Select risultino simili anche se in realtà il tipo degli oggetti creati dalle espressioni lambda è diverso.

Espressioni lambda

Un'espressione lambda con un'espressione a destra dell'operatore => è denominata espressione lambda. Un'espressione lambda dell'espressione restituisce il risultato dell'espressione e ha il formato di base seguente:

(input-parameters) => expression

Il corpo di un'espressione lambda può essere costituito da una chiamata al metodo. Tuttavia, se si creano alberi delle espressioni valutati al di fuori del contesto di .NET Common Language Runtime (CLR), ad esempio in SQL Server, non è consigliabile usare chiamate di metodo nelle espressioni lambda. I metodi non avranno alcun significato al di fuori del contesto di .NET Common Language Runtime (CLR).

Espressioni lambda dell'istruzione

Un'istruzione lambda è simile a un'espressione lambda, ad eccezione del fatto che le relative istruzioni sono racchiuse tra parentesi graffe:

(input-parameters) => { <sequence-of-statements> }

Il corpo di un'espressione lambda dell'istruzione può essere costituito da un numero qualsiasi di istruzioni, sebbene in pratica generalmente non ce ne siano più di due o tre.

Action<string> greet = name =>
{
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

Non è possibile usare le istruzioni lambda per creare alberi delle espressioni.

Parametri di input di un'espressione lambda

È possibile racchiudere i parametri di input di un'espressione lambda tra parentesi. Specificare zero parametri di input con parentesi vuote:

Action line = () => Console.WriteLine();

Se un'espressione lambda ha un solo parametro di input, le parentesi sono facoltative:

Func<double, double> cube = x => x * x * x;

Due o più parametri di input sono separati da virgole:

Func<int, int, bool> testForEquality = (x, y) => x == y;

A volte il compilatore non può dedurre i tipi di parametri di input. È possibile specificare i tipi in modo esplicito come illustrato nell'esempio seguente:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

I parametri di input devono essere tutti di tipo esplicito o tutti di tipo implicito. In caso contrario, si verifica un errore del compilatore CS0748.

A partire da C# 9.0, è possibile usare le eliminazioni per specificare due o più parametri di input di un'espressione lambda che non vengono usati nell'espressione:

Func<int, int, int> constant = (_, _) => 42;

I parametri di eliminazione lambda possono essere utili quando si usa un'espressione lambda per fornire un gestore eventi.

Nota

Per la compatibilità con le versioni precedenti, se solo un singolo parametro di input è denominato _, quindi, all'interno di un'espressione lambda, _ viene considerato come nome di tale parametro.

Espressioni lambda asincrone

È facile creare istruzioni ed espressioni lambda che includono l'elaborazione asincrona utilizzando le parole chiave async e await . Nell'esempio seguente di Windows Form è presente un gestore eventi che chiama e attende un metodo asincrono, 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);
    }
}

È possibile aggiungere lo stesso gestore eventi utilizzando un'espressione lambda asincrona. Per aggiungere il gestore, aggiungere un modificatore async prima dell'elenco di parametri lambda, come illustrato di seguito:

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);
    }
}

Per altre informazioni su come creare e usare metodi asincroni, vedere Programmazione asincrona con asincrona e await.

Espressioni lambda e tuple

Il linguaggio C# offre il supporto predefinito per le tuple. È possibile specificare una tupla come argomento di un'espressione lambda e l'espressione lambda può restituire una tupla. In alcuni casi, il compilatore C# usa l'inferenza del tipo per determinare i tipi di componenti della tupla.

Per definire una tupla, è necessario racchiudere tra parentesi un elenco di componenti delimitato da virgole. L'esempio riportato sotto usa una tupla con tre componenti per passare una sequenza di numeri a un'espressione lambda, la quale raddoppia ogni valore e restituisce una tupla con tre componenti che contiene il risultato delle moltiplicazioni.

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)

In genere, i campi di una tupla sono denominati Item1, Item2e così via. È possibile tuttavia definire una tupla usando i componenti denominati, come illustra l'esempio seguente.

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}");

Per altre informazioni sulle tuple C#, vedere Tipi di Tuple.

Espressioni lambda con operatori query standard

LINQ to Objects, tra altre implementazioni, ha un parametro di input il cui tipo appartiene alla famiglia Func<TResult> di delegati generici. Questi delegati usano parametri di tipo per definire il numero e il tipo di parametri di input e il tipo restituito del delegato. Func i delegati sono utili per incapsulare espressioni definite dall'utente applicate a ogni elemento in un set di dati di origine. Considerare ad esempio il tipo delegato Func<T,TResult>:

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

È possibile creare un'istanza Func<int, bool> del delegato, dove int è un parametro di input e bool è il valore restituito. Il valore restituito è sempre specificato nell'ultimo parametro di tipo. Func<int, string, bool>, ad esempio, definisce un delegato con due parametri di input, int e string, e un tipo restituito bool. Quando viene richiamato, il delegato Func seguente restituisce il valore booleano indicante se il parametro di input è uguale a cinque:

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

È anche possibile specificare un'espressione lambda quando il tipo di argomento è Expression<TDelegate>, ad esempio negli operatori query standard definiti nel tipo Queryable. Quando si specifica un argomento Expression<TDelegate>, l'espressione lambda viene compilata per un albero delle espressioni.

L'esempio seguente usa l'operatore query standard 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)}");

Il compilatore è in grado di dedurre il tipo del parametro di input oppure è possibile specificarlo in modo esplicito. Questa espressione lambda particolare conta i numeri interi (n) che divisi per due danno il resto di 1.

L'esempio seguente crea una sequenza contenente tutti gli elementi presenti nella matrice numbers che si trovano a sinistra di 9, vale a dire il primo numero della sequenza che non soddisfa la condizione:

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

In questo esempio viene illustrato come specificare più parametri di input racchiudendoli tra parentesi. Il metodo restituisce tutti gli elementi della numbers matrice finché non trova un numero il cui valore è minore della posizione ordinale nella matrice:

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

Non si usano espressioni lambda direttamente nelle espressioni di query, ma è possibile usarle nelle chiamate di metodo all'interno di espressioni di query, come illustrato nell'esempio seguente:

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

Inferenza del tipo nelle espressioni lambda

Quando si scrivono espressioni lambda, spesso non occorre specificare un tipo per i parametri di input, perché il compilatore può dedurlo in base al corpo dell'espressione lambda, ai tipi di parametro e ad altri fattori, come descritto nelle specifiche del linguaggio C#. Per la maggior parte degli operatori di query standard, il primo input è il tipo degli elementi nella sequenza di origine. Se si esegue una query su , IEnumerable<Customer>la variabile di input viene dedotto come oggetto Customer , il che significa che è possibile accedere ai metodi e alle proprietà seguenti:

customers.Where(c => c.City == "London");

Di seguito sono riportate le regole generali per l'inferenza del tipo nelle espressioni lambda:

  • L'espressione lambda deve contenere lo stesso numero di parametri del tipo delegato.
  • Ogni parametro di input nell'espressione lambda deve essere convertibile in modo implicito nel parametro del delegato corrispondente.
  • Il valore restituito dell'espressione lambda, se presente, deve essere convertibile in modo implicito nel tipo restituito del delegato.

Tipo naturale di un'espressione lambda

Un'espressione lambda in sé non ha un tipo perché il sistema di tipi comuni non ha alcun concetto intrinseco di "espressione lambda". Tuttavia, a volte è utile parlare in modo informale del "tipo" di un'espressione lambda. Tale "tipo" informale fa riferimento al tipo delegato o Expression al tipo a cui viene convertita l'espressione lambda.

A partire da C# 10, un'espressione lambda può avere un tipo naturale. Anziché forzare l'utente a dichiarare un tipo delegato, ad Func<...> esempio o Action<...> per un'espressione lambda, il compilatore può dedurre il tipo di delegato dall'espressione lambda. Si consideri ad esempio la seguente dichiarazione:

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

Il compilatore può dedurre parse come un Func<string, int>oggetto . Il compilatore sceglie un delegato o Action disponibileFunc, se esiste uno appropriato. In caso contrario, sintetizza un tipo delegato. Ad esempio, il tipo delegato viene sintetizzato se l'espressione lambda ha ref parametri. Quando un'espressione lambda ha un tipo naturale, può essere assegnata a un tipo meno esplicito, ad esempio System.Object o System.Delegate:

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

I gruppi di metodi , ovvero i nomi dei metodi senza elenchi di parametri, con un overload con un tipo naturale:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

Se si assegna un'espressione lambda a System.Linq.Expressions.LambdaExpressiono System.Linq.Expressions.Expressione il tipo lambda ha un tipo delegato naturale, l'espressione ha un tipo naturale di System.Linq.Expressions.Expression<TDelegate>, con il tipo delegato naturale usato come argomento per il parametro di tipo:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

Non tutte le espressioni lambda hanno un tipo naturale. Prendere in considerazione la dichiarazione seguente:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

Il compilatore non può dedurre un tipo di parametro per s. Quando il compilatore non può dedurre un tipo naturale, è necessario dichiarare il tipo:

Func<string, int> parse = s => int.Parse(s);

Tipo restituito esplicito

In genere, il tipo restituito di un'espressione lambda è ovvio e dedotto. Per alcune espressioni, che non funzionano:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

A partire da C# 10, è possibile specificare il tipo restituito di un'espressione lambda prima dei parametri di input. Quando si specifica un tipo restituito esplicito, è necessario parentesi i parametri di input:

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

Attributi

A partire da C# 10, è possibile aggiungere attributi a un'espressione lambda e ai relativi parametri. Nell'esempio seguente viene illustrato come aggiungere attributi a un'espressione lambda:

Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

È anche possibile aggiungere attributi ai parametri di input o al valore restituito, come illustrato nell'esempio seguente:

var sum = ([Example(1)] int a, [Example(2), Example(3)] int b) => a + b;
var inc = [return: Example(1)] (int s) => s++;

Come illustrato negli esempi precedenti, è necessario parentesi i parametri di input quando si aggiungono attributi a un'espressione lambda o ai relativi parametri.

Importante

Le espressioni lambda vengono richiamate tramite il tipo delegato sottostante. Diversa da metodi e funzioni locali. Il metodo del Invoke delegato non controlla gli attributi nell'espressione lambda. Gli attributi non hanno alcun effetto quando viene richiamata l'espressione lambda. Gli attributi delle espressioni lambda sono utili per l'analisi del codice e possono essere individuati tramite reflection. Una conseguenza di questa decisione è che non è possibile applicare a un'espressione System.Diagnostics.ConditionalAttribute lambda.

Acquisire variabili esterne e ambito delle variabili nelle espressioni lambda

Le espressioni lambda possono fare riferimento a variabili esterne. Si tratta delle variabili incluse nell'ambito del metodo che definisce l'espressione lambda oppure nell'ambito del tipo che contiene l'espressione lambda. Le variabili acquisite in questo modo vengono archiviate per poter essere utilizzate nell'espressione lambda anche se le variabili diventano esterne all'ambito e vengono sottoposte a Garbage Collection. Una variabile esterna deve essere assegnata prima di poter essere utilizzata in un'espressione lambda. Nell'esempio seguente vengono illustrate queste regole:

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
}

Le regole seguenti si applicano all'ambito delle variabili nelle espressioni lambda:

  • Una variabile acquisita non verrà raccolta in garbage finché il delegato a cui fa riferimento diventa idoneo per Garbage Collection.
  • Le variabili introdotte all'interno di un'espressione lambda non sono visibili nel metodo di inclusione.
  • Un'espressione lambda non può acquisire direttamente un parametro in, ref o out dal metodo di inclusione.
  • Un'istruzione return in un'espressione lambda non causa la restituzione del metodo contenitore.
  • Un'espressione lambda non può contenere un'istruzione goto, interruzione o continua se la destinazione di tale istruzione jump non è esterna al blocco di espressioni lambda. È anche errato inserire all'esterno del blocco dell'espressione lambda un'istruzione di salto se la destinazione è interna al blocco.

A partire da C# 9.0, è possibile applicare il static modificatore a un'espressione lambda per impedire l'acquisizione involontaria di variabili locali o lo stato dell'istanza tramite lambda:

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

Una lambda statica non può acquisire variabili locali o stato dell'istanza da ambiti racchiusi, ma può fare riferimento a membri statici e definizioni costanti.

Specifiche del linguaggio C#

Per altre informazioni, vedere la sezione Espressioni di funzioni anonime della specifica del linguaggio C#.

Per altre informazioni sulle funzionalità aggiunte in C# 9.0 e versioni successive, vedere le note sulla proposta di funzionalità seguenti:

Vedi anche