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 come Func<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.

Altre informazioni e risorse