Sostieni AppuntiFacili con una piccola donazione su PayPal
Dona con PayPalUn delegato è un tipo che incapsula in modo sicuro un riferimento a uno o più metodi. In pratica, è un puntatore a funzione tipizzato, che consente di:
Per dichiarare un tipo delegato si utilizza la parola chiave delegate:
[modificatore] delegate [tipo_ritorno] [nome_funzione]([lista_parametri]);
Esempio:
public class Program
{
// dichiarazione delegato
public delegate void Delegato(string messaggio);
// metodo che fa riferimento alla firma del delegato
public static void MetodoStampa(string messaggio)
{
Console.WriteLine(messaggio);
}
public static void Main()
{
// instanzia il delegato
Delegato del = MetodoStampa;
// invocazione del delegato
del("Hello World");
}
}
In questo esempio:
Delegato rappresenta un metodo che non restituisce nulla (void) e accetta una stringa.MetodoStampa è compatibile con il delegato, quindi può essere assegnato.del("Hello World!") equivale a chiamare MetodoStampa("Hello World!").Un delegato multicast può referenziare più metodi contemporaneamente.
Regole principali:
public delegate void Delegato(string messaggio);
public class Program
{
public static void MetodoA(string msg) => Console.WriteLine("A: " + msg);
public static void MetodoB(string msg) => Console.WriteLine("B: " + msg);
public static void MetodoC(string msg) => Console.WriteLine("C: " + msg);
public static void Main()
{
Delegato d = MetodoA;
d += MetodoB;
d += MetodoC;
d("Prova di multicast"); // Invoca A, poi B, poi C
d -= MetodoB;
d("Dopo rimozione di B"); // Invoca A e C
}
}
Quando un delegato rappresenta metodi che ritornano un valore, solo il valore restituito dall’ultimo metodo della lista multicast viene mantenuto.
Esempio:
public delegate int Calcola();
public class Program
{
public static int Somma() => 10;
public static int Moltiplica() => 5 * 2;
public static void Main()
{
Calcola calc = Somma;
calc += Moltiplica;
int risultato = calc(); // ritorna 10 (Somma) e poi 10 (Moltiplica) ma mantiene l'ultimo risultato
Console.WriteLine(risultato); // 10
}
}
C# fornisce delegati generici già pronti per l’uso, che evitano di dichiarare nuovi tipi delegate.
Rappresenta un metodo che non restituisce valore (void)
Action<string> stampa = msg => Console.WriteLine(msg);
stampa("Ciao da Action!");
Rappresenta un metodo che restituisce un valore.
Func<int, int, int> somma = (a, b) => a + b;
int risultato = somma(5, 4); // 7
L’ultimo tipo generico rappresenta il tipo di ritorno.
Esempio:
Func<string, int> accetta una string, restituisce un int.
Rappresenta un metodo che restituisce un valore booleano (bool) e accetta un singolo parametro.
Predicate<int> pari = n => n % 2 == 0;
Console.WriteLine(pari(4)); // True
È possibile definire un delegato senza dichiararne esplicitamente il tipo senza creare un metodo separato.
Esempio:
public delegate void Stampa(string messaggio);
Stampa stampa = delegate(string msg)
{
Console.WriteLine($"Anonimo: {msg}");
};
stampa("Hello!");
Questa forma è utile quando la logica del metodo è semplice o usata una sola volta.
Le lambda expressions sono una forma compatta per definire metodi anonimi.
Action<int> stampaQuadrato = n => Console.WriteLine(n * n);
stampaQuadrato(5); // 25
Sono comunemente utilizzate con LINQ e metodi di estansione come Select, Where, OrderBy.
Esempio LINQ:
int[] numeri = {1, 2, 3, 4, 5};
var pari = numeri.Where(n => n % 2 == 0);
foreach (var n in pari)
{
Console.WriteLine(n);
}
Gli eventi in C# si basano proprio sui delegati. Un evento è un meccanismo che permette a una classe di notificare a una o più altre classi che qualcosa è successo.
Esempio:
public delegate void Notifica(string message);
public class Publisher
{
public event Notifica OnNotifica;
public void Invia()
{
OnNotifica?.Invoke("Evento generato!");
}
}
public class Subscriber
{
public void Ricevi(string msg)
{
Console.WriteLine($"Ricevuto: {msg}");
}
}
public class Program
{
public static void Main()
{
Publisher pub = new Publisher();
Subscriber sub = new Subscriber();
pub.OnNotifica += sub.Ricevi;
pub.Invia(); // Ricevuto: Evento generato!
}
}
I delegati supportano la covarianza e la controvarianza, cioè la possibilità di utilizzare tipi derivati o base in modo compatibile con la firma del delegato.
public class Animale { }
public class Cane : Animale { }
public delegate Animale CreaAnimale();
public delegate void GestisciAnimale(Cane c);
CreaAnimale d1 = () => new Cane(); // Covarianza
GestisciAnimale d2 = (Animale a) => Console.WriteLine(a.GetType().Name); // Controvarianza
Scenario: Vuoi creare un piccolo motore di calcolo personalizzabile, dove l’utente può scegliere che tipo di operazione applicare su due numeri.
Consegna:
Operazione(int a, int b) che restituisce un int.Somma(int a, int b)Differenza(int a, int b)Moltiplicazione(int a, int b)EseguiOperazione(int a, int b, Operazione op) che invoca il delegato passato.Main, chiedi all’utente di scegliere l’operazione da eseguire e usa il delegato corrispondente.Obiettivo didattico: capire come passare comportamenti come parametri, e come cambiare la logica di un metodo a runtime.
Scenario: Hai un’applicazione che deve registrare log in modo diverso a seconda del contesto (console, file, o rete). Vuoi che il tipo di logging sia pluggabile tramite delegati.
Consegna:
Logger(string messaggio).LogConsole(string msg) → scrive in console.LogFile(string msg) → scrive su file log.txt.LogNetwork(string msg) → simula l’invio di un log in rete (es. Console.WriteLine("Inviato: " + msg)).RegistraOperazione(string azione, Logger log) che invoca il logger passato.Main, esegui varie chiamate con diversi logger e mostra come cambia il comportamento.Obiettivo didattico: imparare a decouplare la logica di logging dal codice principale, passando comportamenti dinamici tramite delegati.
Scenario:
Hai una lista di numeri interi e vuoi filtrarla in modo dinamico (pari, dispari, maggiori di N, ecc.) usando delegati o Predicate<int>.
Consegna:
Filtra(List<int> numeri, Predicate<int> condizione) che restituisce una nuova lista contenente solo gli elementi che rispettano la condizione.Obiettivo didattico: comprendere come usare delegati predefiniti (Predicate, Func) per scrivere codice generico e riutilizzabile.
Scenario: Vuoi simulare un contatore di download che notifica gli utenti quando viene raggiunto un certo numero di download.
Consegna:
DownloadHandler(string messaggio).DownloadManager con:
OnLimiteRaggiuntoconteggioRegistraDownload() che incrementa il contatore e, se arriva a 5, genera l’evento.EmailNotifier che stampa: “Email inviata: messaggio”ConsoleNotifier che stampa: “Notifica: messaggio”Main, collega entrambe le classi all’evento e simula più download.Obiettivo didattico: collegare il concetto di delegato → evento → subscriber, come accade nei sistemi reali.
Prenota una lezione