Espressioni lambda (Guida per programmatori C#)

Un'espressione lambda è una funzione anonima che può contenere espressioni e istruzioni e che può essere utilizzata per creare delegati o tipi di struttura ad albero dell'espressione.

Tutte le espressioni lambda utilizzano l'operatore lambda =>, che viene letto come "fino a". Il lato sinistro dell'operatore lambda specifica i parametri di input, se presenti, e il lato destro contiene l'espressione o il blocco di istruzioni. L'espressione lambda x => x * x viene letta "x fino a x per x". Questa espressione può essere assegnata a un tipo delegato come segue:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

Per creare un tipo di struttura ad albero dell'espressione:

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

L'operatore => ha la stessa precedenza dell'operatore di assegnazione (=) e prevede l'associazione all'operando di destra.

Le espressioni lambda vengono utilizzate nelle query LINQ basate su metodo come argomenti dei metodi degli operatori di query standard come Where.

Quando si utilizza la sintassi basata sul metodo per chiamare il metodo Where nella classe Enumerable, come in LINQ to Objects e LINQ to XML, il parametro è un tipo delegato System.Func<T, TResult>. Un'espressione lambda rappresenta il modo più appropriato per creare tale delegato. Quando ad esempio si chiama lo stesso metodo nella classe System.Linq.Queryable, come in LINQ to SQL, il tipo di parametro sarà System.Linq.Expressions.Expression<Func>, dove Func è qualsiasi delegato Func con un massimo di sedici parametri di input. Un'espressione lambda rappresenta quindi un modo rapido per costruire la struttura ad albero dell'espressione. Le espressioni lambda consentono alle chiamate Where di risultare simili sebbene in realtà il tipo di oggetto creato dall'espressione lambda sia diverso.

Nell'esempio precedente notare che la firma del delegato ha un parametro di input tipizzato in modo implicito di tipo int e restituisce un oggetto int. L'espressione lambda può essere convertita in un delegato di quel tipo poiché ha anche un parametro di input (x) e un valore restituito che il compilatore può convertire in modo implicito nel tipo int. L'inferenza dei tipi viene illustrata più dettagliatamente nelle sezioni seguenti. Quando il delegato viene richiamato utilizzando un parametro di input di 5, restituisce un risultato di 25.

Non è possibile utilizzare le espressioni lambda sul lato sinistro dell'operatore is o as.

Tutte le restrizioni che si applicano ai metodi anonimi si applicano anche alle espressioni lambda. Per ulteriori informazioni, vedere Metodi anonimi (Guida per programmatori C#).

Espressioni lambda dell'espressione

Un'espressione lambda con un'espressione sul lato destro viene chiamata espressione lambda dell'espressione. Le espressioni lambda dell'espressione vengono utilizzate spesso nella costruzione di Strutture ad albero dell'espressione (C# e Visual Basic). Un'espressione lambda dell'espressione restituisce il risultato dell'espressione e accetta il seguente form di base:

(input parameters) => expression

Le parentesi sono facoltative solo se l'espressione lambda ha un parametro di input; in caso contrario sono necessarie. Due o più parametri di input vengono separati da virgole racchiusi tra parentesi:

(x, y) => x == y

Talvolta è difficile o impossibile che il compilatore possa dedurre i tipi di input. In tal caso, è possibile specificare i tipi in modo esplicito come illustrato nell'esempio seguente:

(int x, string s) => s.Length > x

Specificare zero parametri di input con le parentesi vuote:

() => SomeMethod()

Notare nell'esempio precedente che il corpo di un'espressione lambda dell'espressione può essere costituito da una chiamata al metodo. Tuttavia, se si creano strutture ad albero dell'espressione da utilizzare in un altro dominio, ad esempio SQL Server, non utilizzare le chiamate al metodo nelle espressioni lambda. I metodi non avranno significato all'esterno del contesto di .NET Common Language Runtime.

Espressioni lambda dell'istruzione

Un'espressione lambda dell'istruzione è simile a un'espressione lambda dell'espressione con la differenza che l'istruzione viene racchiusa tra parentesi graffe:

(input parameters) => {statement;}

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

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Le espressioni lambda dell'istruzione, come i metodi anonimi, non possono essere utilizzate per creare strutture ad albero dell'espressione.

Espressioni lambda con gli operatori di query standard

Molti operatori di query standard hanno un parametro di input il cui tipo è uno della famiglia Func<T, TResult> dei delegati generici. I delegati Func<T, TResult> utilizzano i parametri di tipo per definire il numero e il tipo di parametri di input e il tipo restituito del delegato. I delegati Func sono molto utili per incapsulare le espressioni definite dall'utente applicate a ogni elemento in un insieme di dati di origine. Considerare ad esempio il seguente tipo delegato:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

È possibile creare un'istanza del delegato come Func<int,bool> myFunc 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> definisce un delegato con due parametri di input, int e string, e un tipo restituito bool. Il seguente delegato Func, quando viene richiamato, restituirà true o false per indicare se il parametro di input è uguale a 5:

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

È inoltre possibile fornire un'espressione lambda quando il tipo di argomento è Expression<Func>, ad esempio negli operatori di query standard definiti in System.Linq.Queryable. Quando si specifica un argomento Expression<Func>, l'espressione lambda verrà compilata in una struttura ad albero dell'espressione.

Di seguito viene illustrato un operatore di query standard, il metodo Count:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

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

Il metodo seguente produce una sequenza che contiene tutti gli elementi presenti nella matrice numbers che si trovano a sinistra di 9, che rappresenta il primo numero della sequenza che non soddisfa la condizione:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

In questo esempio viene illustrato come specificare più parametri di input racchiudendoli tra parentesi. Il metodo restituisce tutti gli elementi presenti nella matrice di numeri finché non viene rilevato un numero il cui valore sia inferiore alla posizione del numero stesso. Non confondere l'operatore lambda (=>) con l'operatore maggiore di o uguale a (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

Inferenza dei tipi nelle espressioni lambda

Quando si scrivono le espressioni lambda, spesso non è necessario specificare un tipo per i parametri di input poiché il compilatore è in grado di dedurre il tipo in base al corpo dell'espressione lambda, al tipo delegato sottostante e ad altri fattori, come descritto nella specifica del linguaggio C#. Per la maggior parte degli operatori di query standard, il primo input è il tipo degli elementi presenti nella sequenza di origine. Pertanto se si esegue una query su un oggetto IEnumerable<Customer>, si deduce che la variabile di input è 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 le 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.

Notare che le espressioni lambda non hanno un tipo poiché il sistema di tipi comuni non ha alcun concetto intrinseco di "espressione lambda". Tuttavia, a volte è comodo parlare in modo informale del "tipo" di un'espressione lambda. In questi casi il tipo si riferisce al tipo delegato o al tipo Expression in cui viene convertita l'espressione lambda.

Ambito delle variabili nelle espressioni lambda

Le espressioni lambda possono fare riferimento a variabili esterne che rientrano nell'ambito del metodo o del tipo contenitore in cui viene definita 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 assolutamente assegnata prima di poter essere utilizzata in un'espressione lambda. Nell'esempio seguente vengono illustrate queste regole:

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

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

  • Una variabile acquisita non sarà sottoposta Garbage Collection finché il delegato a cui fa riferimento non diventa esterno all'ambito.

  • Le variabili introdotte all'interno di un'espressione lambda non sono visibili nel metodo esterno.

  • Un'espressione lambda non può acquisire direttamente un parametro ref o out da un 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, un'istruzione break o un'istruzione continue la cui destinazione è all'esterno del corpo o nel corpo di una funzione anonima contenuta.

Specifiche del linguaggio C#

Per ulteriori informazioni, vedere la Specifiche del linguaggio C#. La specifica del linguaggio è la fonte ufficiale per la sintassi e l'utilizzo di C#.

Capitolo del libro rappresentati

Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Vedere anche

Riferimenti

Metodi anonimi (Guida per programmatori C#)

is (Riferimenti per C#)

Concetti

Guida per programmatori C#

Strutture ad albero dell'espressione (C# e Visual Basic)

Altre risorse

LINQ (Language-Integrated Query)

Espressioni lambda ricorsive