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 | |
|
| 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); });
}
GO TO FULL VERSION