Espressioni lambda (Guida per programmatori C#)
Un'espressione lambda è una funzione anonima che è possibile utilizzare per creare delegati o i tipi di struttura ad albero dell'espressione.Utilizzando espressioni lambda, è possibile scrivere funzioni locali che possono essere passate come argomenti o essere restituito come valore di chiamate di funzione.Le espressioni lambda sono particolarmente utili per scrivere espressioni di query LINQ.
Per creare un'espressione lambda, specificare i parametri di input (se presenti) a sinistra dell'operatore lambda =>e inserire l'espressione o il blocco di istruzioni dall'altro lato.Ad esempio, l'espressione lambda x => x * x specifica un parametro denominato x e restituisce il valore x ha quadrato.È possibile assegnare questa espressione a un tipo delegato, come illustrato nel seguente esempio:
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.
Async espressioni lambda
È possibile creare espressioni lambda e le istruzioni che includono l'elaborazione asincrona utilizzando le parole chiave await e async.Ad esempio, il seguente esempio di Windows Form contiene un gestore eventi che chiama e attende un metodo async, ExampleMethodAsync.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
È possibile aggiungere lo stesso gestore eventi utilizzando un async lambda.Per aggiungere il gestore, aggiungere un modificatore async prima dell'elenco di parametri di lambda, come illustrato di seguito.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
};
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Per ulteriori informazioni su come creare e utilizzare i metodi async, vedere Programmazione asincrona con Async e Await (C# e Visual Basic).
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 seguente metodo genererà una sequenza contenente tutti gli elementi nella matrice numbers che sono a sinistra laterali di 9 perché si tratta del primo numero in 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#.
Capitoli 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#)
Concetti
Strutture ad albero dell'espressione (C# e Visual Basic)