CodeGym /Corsi /C# SELF /Introduzione ai metodi anonimi (

Introduzione ai metodi anonimi ( delegate)

C# SELF
Livello 49, Lezione 0
Disponibile

1. Introduzione

Quando programmi, a volte serve descrivere una semplice azione che viene usata solo una volta e non servirà altrove. Creare un metodo separato per questo è come piantare un chiodo con un martello preso da un kit per assemblare un aereo. Molto più semplice prendere qualcosa di pratico e risolvere il compito "sul posto".

Metodo anonimo — è un pezzo di codice temporaneo che non ha bisogno di un nome perché viene usato proprio dove viene creato. Esso:

  • Viene dichiarato all'interno di un altro metodo.
  • Non ha un nome.
  • Di solito viene usato una sola volta — per esempio passato a un delegate o sottoscritto a un evento.

Oggi si usano più spesso le lambda expressions (l'operatore =>), ma capire i metodi anonimi aiuta a comprendere come sono fatti i delegate e il modello degli eventi in C#.

Quando un metodo anonimo è comodo?

  • Quando devi passare velocemente un pezzetto di logica come parametro, per esempio per sorting, filtering, eventi ecc.
  • Non ha senso aggiungere metodi separati nella classe per logica usa-e-getta.
  • Quando vuoi che il codice sia compatto e leggibile: tutta la "piccola business logic" in un solo posto.

Un po' di storia

Per la prima volta in C# la possibilità di creare metodi anonimi è arrivata nella versione 2.0. Prima era macchinoso usare i delegate: bisognava dichiarare un metodo nominativo anche se la sua logica serviva solo in un punto. Con l'introduzione dei metodi anonimi tutto si è semplificato, e con l'arrivo delle lambda expressions l'idea è diventata ancora più concisa.

2. La classica: delegate e metodo "nominativo" tradizionale

Prima di conoscere i metodi anonimi, ricordiamo il modo standard di passare comportamento tramite delegate. Supponiamo di avere un'app "Database di libri", dove vogliamo filtrare i libri secondo diverse condizioni.

// Definiamo il delegate
public delegate bool BookFilter(Book book);

// Classe Book
public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }
}

Una volta scrivevamo così:

// Funzione-filtro separata
public static bool IsClassic(Book b)
{
    return b.Year < 1970;
}

// Da qualche parte nel codice di utilizzo
BookFilter filter = IsClassic;

Dichiarare ogni volta un metodo separato non è sempre comodo. E se i filtri sono decine?

3. Metodo anonimo: minimalismo, amico dei delegate

Un metodo anonimo permette di definire il codice del filtro proprio nel punto in cui serve:

BookFilter filter = delegate(Book b)
{
    return b.Year < 1970;
};

Questo è tutto! Niente metodi in più, tutto sul posto. delegate funge da dichiarazione di funzione senza nome.

Sintassi generale

delegate([argomenti])
{
    // corpo del metodo
};

Esempio integrato nel programma:

public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }
}

public delegate bool BookFilter(Book book);

class Program
{
    static void Main()
    {
        Book[] books = {
            new Book { Title = "Master i Margarita", Year = 1967 },
            new Book { Title = "Clean Code", Year = 2008 }
        };

        // Filtro anonimo per i classici
        BookFilter filter = delegate(Book b)
        {
            return b.Year < 1970;
        };

        foreach (Book book in books)
        {
            if (filter(book)) // chiamiamo la funzione anonima!
                Console.WriteLine($"{book.Title} — classico!");
        }
    }
}

Output:

Master i Margarita — classico!

4. Metodo anonimo con diversi delegate

I metodi anonimi funzionano bene con tutti i delegate. Per esempio gli standard Action e Func<T, TResult>, che tratteremo più dettagliatamente dopo.

Action<string> sayHello = delegate(string name)
{
    Console.WriteLine($"Ciao, {name}!");
};

sayHello("Mondo"); // Ciao, Mondo!

Breve illustrazione: come funziona un metodo anonimo "usa-e-getta"

È più semplice immaginare il metodo anonimo come un lavoratore temporaneo: arriva per un compito breve, lo esegue — e poi scompare. Colleghiamo lavoratore e compito tramite un delegate.

Func<int, int, int> sum = delegate (int a, int b) {
    return a + b;
};
Console.WriteLine(sum(5, 7)); // 12

Applicazioni: sorting, ricerca, processing di collezioni

Supponiamo di avere una lista di libri che vogliamo ordinare per anno di pubblicazione. Dichiarare un metodo separato solo per il confronto è eccessivo.

var books = new List<Book>
{
    new Book { Title = "Master i Margarita", Year = 1967 },
    new Book { Title = "Clean Code", Year = 2008 }
};

books.Sort(delegate(Book a, Book b)
{
    return a.Year.CompareTo(b.Year);
});

foreach (var book in books)
    Console.WriteLine($"{book.Title} ({book.Year})");

5. Dettagli utili

Il metodo anonimo non è la stessa cosa della lambda expression?

Nel C# moderno si usano più spesso le lambda expressions (=>), e di solito la differenza è minima, ma ci sono differenze importanti:

  • Le lambda sono più corte ed espressive.
  • Le lambda hanno regole di cattura delle variabili più chiare (impareremo questo più avanti).
  • I metodi anonimi sono apparsi prima e a volte si trovano in codice "ereditato" di progetti più vecchi.

Così sarebbe il nostro esempio con una lambda:

BookFilter filter = b => b.Year < 1970;

Comunque, sapere come funziona sia la sintassi vecchia che quella nuova è utile per colloqui, leggere codice altrui e capire profondamente i delegate.

Metodi anonimi con o senza parametri

Se il delegate non prende parametri, puoi scriverlo in un'unica riga:

Action printHello = delegate { Console.WriteLine("Ciao!"); };
printHello(); // Ciao!

Se prende parametri — si può comunque scrivere in una riga:

Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36

Metodo anonimo e Lambda expression

Metodo anonimo Lambda expression
Sintassi
delegate (...) {}
(args) => {}
Cattura variabili Sì Sì
Popolarità Usato raramente Standard
Return value Possibile/obbligatorio Possibile/obbligatorio
Multi-line Possibile Possibile

6. Metodi anonimi — dettagli e sfumature d'uso

Cattura di variabili locali
I metodi anonimi possono usare variabili dal metodo circostante — questo meccanismo si chiama "closure". Per esempio:

int minYear = 1970;

BookFilter filter = delegate(Book book)
{
    return book.Year < minYear;
};

Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True

Se più tardi modifichi la variabile minYear, il filtro userà il nuovo valore!

È possibile non specificare i parametri
Se i parametri non servono:

Action sayHi = delegate { Console.WriteLine("Hi!"); };

Passare null
Se a un delegate non è assegnato alcun metodo (per esempio anonimo), il suo valore è null, e il tentativo di invocarlo genera una NullReferenceException. Attenzione a questo.

Multi-line
Un metodo anonimo può contenere un blocco completo di codice, condizioni, loop e persino altre chiamate:

Action manyThings = delegate
{
    Console.WriteLine("Inizio!");
    for (int i = 0; i < 3; i++)
        Console.WriteLine(i);
    Console.WriteLine("Fine!");
};
manyThings();

7. Errori tipici e particolarità

A volte i programmatori confondono la scope delle variabili: la cattura di valori da un loop o da un metodo annidato può provocare risultati inaspettati.

List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — sorpresa!

Il punto è che il metodo anonimo "vede" la variabile i — quando il loop finisce, i diventa 3. Tutti i metodi stampano la stessa cosa. Per ottenere il comportamento corretto è meglio catturare la variabile così:

for (int i = 0; i < 3; i++)
{
    int current = i;
    actions.Add(delegate { Console.WriteLine(current); });
}
2
Compito
C# SELF, livello 49, lezione 0
Bloccato
Prima pratica con i metodi anonimi
Prima pratica con i metodi anonimi
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION