Delegati e lambda
Un delegato definisce un tipo che rappresenta i riferimenti ai metodi con un particolare elenco di parametri e un tipo restituito. Un metodo (statico o istanza) con un elenco parametri e un tipo restituito che corrispondono può essere assegnato a una variabile del tipo, quindi chiamato direttamente (con gli argomenti appropriati) o passato come argomento a un altro metodo e quindi chiamato. L'esempio seguente mostra l'uso dei delegati.
using System;
using System.Linq;
public class Program
{
public delegate string Reverse(string s);
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Reverse rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
- La riga
public delegate string Reverse(string s);
crea un tipo delegato di un metodo che accetta un parametro di stringa e quindi restituisce un parametro di stringa. - Il metodo
static string ReverseString(string s)
, che ha esattamente lo stesso elenco di parametri e il tipo restituito del tipo delegato definito, implementa il delegato. - La riga
Reverse rev = ReverseString;
indica che si può assegnare un metodo a una variabile del tipo di delegato corrispondente. - La riga
Console.WriteLine(rev("a string"));
illustra come usare una variabile di un tipo delegato per richiamare il delegato.
Per semplificare il processo di sviluppo, .NET include un set di tipi di delegato che i programmatori possono riutilizzare senza dover creare nuovi tipi. Questi tipi sono Func<>
, Action<>
e Predicate<>
e possono essere usati senza dover definire nuovi tipi delegati. Esistono alcune differenze tra i tre tipi, legate al loro uso previsto:
Action<>
viene usato quando è necessario eseguire un'azione usando gli argomenti del delegato. Il metodo incapsulato non restituisce un valore.Func<>
viene in genere usato in presenza di una trasformazione, ovvero quando è necessario trasformare gli argomenti del delegato in un risultato diverso. Le proiezioni sono un buon esempio. Il metodo che incapsula restituisce un valore specificato.Predicate<>
viene usato quando è necessario determinare se l'argomento soddisfa la condizione del delegato. Può anche essere scritto comeFunc<T, bool>
, il che significa che il metodo restituisce un valore booleano.
L'esempio precedente può essere ora riscritto usando il delegato Func<>
anziché un tipo personalizzato. Il programma continuerà a essere eseguito nello stesso modo.
using System;
using System.Linq;
public class Program
{
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Func<string, string> rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
In questo esempio semplice la presenza di un metodo definito all'esterno del metodo Main
non è necessaria. .NET Framework 2.0 ha introdotto il concetto di delegati anonimi, che consentono di creare delegati "inline" senza dover specificare alcun tipo o metodo aggiuntivo.
Nell'esempio seguente un delegato anonimo filtra un elenco solo per i numeri pari e quindi li stampa nella console.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(
delegate (int no)
{
return (no % 2 == 0);
}
);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
È possibile osservare che il corpo del delegato è costituito da un set di espressioni, come qualsiasi altro delegato. Ma anziché essere una definizione separata, il delegato è stato inserito appositamente nella chiamata al metodo List<T>.FindAll.
Anche con questo approccio, tuttavia, rimane una parte considerevole di codice che è possibile eliminare. A tale scopo, vengono usate le espressioni lambda. Le espressioni lambda, chiamate anche "lambda", sono state usate in C# 3.0 come uno dei componenti fondamentali di Language Integrated Query (LINQ). Si tratta semplicemente una sintassi più pratica per l'uso dei delegati. Dichiarano un elenco parametri e il corpo di un metodo senza avere una propria identità formale, a meno che non vengano assegnate a un delegato. A differenza dei delegati, possono essere assegnate direttamente come parte destra della registrazione eventi o in diverse clausole e metodi LINQ.
Poiché un'espressione lambda è soltanto un modo diverso di specificare un delegato, è possibile riscrivere l'esempio precedente per usare un'espressione lambda anziché un delegato anonimo.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(i => i % 2 == 0);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
Nell'esempio precedente l'espressione lambda usata è i => i % 2 == 0
. Anche in questo caso, si tratta solamente di una sintassi pratica per l'uso dei delegati. Ciò che accade dietro le quinte è simile a quello che accade con il delegato anonimo.
Le lambda sono semplicemente delegati, ovvero possono essere usate come gestori di eventi come mostra il frammento di codice seguente.
public MainWindow()
{
InitializeComponent();
Loaded += (o, e) =>
{
this.Title = "Loaded";
};
}
L'operatore +=
in questo contesto viene usato per eseguire la sottoscrizione a un evento. Per altre informazioni, vedere Come iscriversi agli eventi e annullare l'iscrizione.