Espressioni lambda (Riferimenti per C#)

Usare 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ò avere uno dei due formati 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 lambda , che specifica un parametro denominato e restituisce il valore di quadrato, viene assegnata a una variabile di x => x * x x un tipo x 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 nella classe , ad esempio in LINQ to SQL, il tipo di parametro è un tipo di albero Queryable.Select System.Linq.Queryable 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 al metodo nelle espressioni lambda. I metodi non avranno alcun significato al di fuori del contesto di Common Language Runtime (CLR) .NET.

Espressioni lambda dell'istruzione

Un'espressione lambda dell'istruzione è simile a un'espressione lambda con la differenza 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 espressioni lambda dell'istruzione per creare alberi delle espressioni.

Parametri di input di un'espressione lambda

I parametri di input di un'espressione lambda vengono racchiusi 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;

In alcuni casi 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 discards 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 discard lambda possono essere utili quando si usa un'espressione lambda per fornire un gestore eventi.

Nota

Per la compatibilità con le versioni precedenti, se un solo parametro di input è denominato , all'interno di un'espressione lambda viene considerato come _ il 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 async e await.

Espressioni lambda e tuple

A partire da C# 7.0, il linguaggio C# fornisce 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 Item2 , e così via. È tuttavia possibile definire una tupla con componenti denominati, come nell'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 tupla.

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. I delegatiFunc sono molto utili per incapsulare le 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 presenti nella matrice numbers finché non viene rilevato un numero il cui valore è inferiore alla relativa posizione all'interno della 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

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. Pertanto, se si esegue una query su un oggetto IEnumerable<Customer>, si deduce che la variabile di input sia un oggetto Customer, ovvero che si dispone dell'accesso ai relativi metodi e proprietà:

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.

Si noti che le espressioni lambda non hanno un tipo perché Common Type System non ha alcun concetto intrinseco di "espressione lambda". In alcuni casi, tuttavia, può essere utile fare riferimento in modo informale al "tipo" di un'espressione lambda. In questi casi, per tipo si intende il tipo delegato o il tipo Expression in cui viene convertita l'espressione 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 sarà sottoposta a Garbage Collection finché il delegato a cui fa riferimento non diventa idoneo per il Garbage Collection.

  • Le variabili introdotte in un'espressione lambda non sono visibili nel metodo contenitore.

  • Un'espressione lambda non può acquisire direttamente un parametro in, ref o out dal metodo contenitore.

  • Un'istruzione return in un'espressione lambda non causa la restituzione del metodo contenitore.

  • Un'espressione lambda non può contenere un'istruzione goto, break o continue se la destinazione di tale istruzione di salto è esterna al blocco dell'espressione 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 modificatore a un'espressione lambda per impedire l'acquisizione non intenzionale delle variabili locali o dello stato dell'istanza da parte static dell'espressione lambda:

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

Un'espressione lambda statica non può acquisire variabili locali o stato dell'istanza dagli ambiti di inclusione, 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, vedere le note sulla proposta di funzionalità seguenti:

Vedi anche