Sostieni AppuntiFacili con una piccola donazione su PayPal

Dona con PayPal
AppuntiFacili
Torna Indietro Segnala errore

Delegates

✍️ Dennis Turco 🏷️ Informatica 📘 C#
Ultima modifica:
#csharp#programmazione#delegati

1. Introduzione

Un 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:

  • Passare metodi come parametri ad altri metodi.
  • Implementare callback ed eventi.
  • Creare codice flessibile e riutilizzabile, in cui il comportamento è definito a runtime.
  • Favorire uno stile funzionale di programmazione (con LINQ, lambda expressions e metodi anonimi).

2. Dichiarazione

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:

  • Il delegato 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!").

3. Multicasting di un delegato

Un delegato multicast può referenziare più metodi contemporaneamente.

Regole principali:

  • Si usano gli operatori + o += per aggiungere metodi.
  • Si usano gli operatori - o -= per rimuoverli.
  • I metodi vengono invocati in ordine FIFO (First In First Out).
  • Se uno dei metodi lancia un’eccezione, l’invocazione si interrompe.
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
    }
}

4. Delegati con tipo di ritorno

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
    }
}

5. Delegati predefiniti: Action, Func e Predicate

C# fornisce delegati generici già pronti per l’uso, che evitano di dichiarare nuovi tipi delegate.

5.1 Action

Rappresenta un metodo che non restituisce valore (void)

Action<string> stampa = msg => Console.WriteLine(msg);
stampa("Ciao da Action!");

5.2 Func

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> \rightarrow accetta una string, restituisce un int.

5.3 Predicate

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

6. Delegati anonimi

È 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.

7. Lambda expressions e delegati

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);
}

8. Delegati ed eventi

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!
    }
}

9. Covarianza e controvarianza dei delegati

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.

  • Covarianza: permette a un delegato di restituire un tipo derivato rispetto a quello dichiarato.
  • Controvarianza: permette a un delegato di accettare un tipo più generico come parametro.
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

10. Esercizi

10.1 Esercizio

Scenario: Vuoi creare un piccolo motore di calcolo personalizzabile, dove l’utente può scegliere che tipo di operazione applicare su due numeri.

Consegna:

  1. Crea un delegato Operazione(int a, int b) che restituisce un int.
  2. Implementa tre metodi statici:
    • Somma(int a, int b)
    • Differenza(int a, int b)
    • Moltiplicazione(int a, int b)
  3. Scrivi un metodo EseguiOperazione(int a, int b, Operazione op) che invoca il delegato passato.
  4. Nel 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.

10.2 Esercizio

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:

  1. Crea un delegato Logger(string messaggio).
  2. Implementa tre metodi diversi:
    • 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)).
  3. Crea un metodo RegistraOperazione(string azione, Logger log) che invoca il logger passato.
  4. Nel 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.

10.3 Esercizio

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:

  1. Crea una lista di interi da 1 a 20.
  2. Crea un metodo Filtra(List<int> numeri, Predicate<int> condizione) che restituisce una nuova lista contenente solo gli elementi che rispettano la condizione.
  3. Usa il metodo tre volte con filtri diversi:
    • Numeri pari
    • Maggiori di 10
    • Multipli di 3
  4. Stampa i risultati a video.

Obiettivo didattico: comprendere come usare delegati predefiniti (Predicate, Func) per scrivere codice generico e riutilizzabile.

10.4 Esercizio

Scenario: Vuoi simulare un contatore di download che notifica gli utenti quando viene raggiunto un certo numero di download.

Consegna:

  1. Crea un delegato DownloadHandler(string messaggio).
  2. Crea una classe DownloadManager con:
    • Un evento OnLimiteRaggiunto
    • Un campo conteggio
    • Un metodo RegistraDownload() che incrementa il contatore e, se arriva a 5, genera l’evento.
  3. Crea due classi “ascoltatrici”:
    • EmailNotifier che stampa: “Email inviata: messaggio”
    • ConsoleNotifier che stampa: “Notifica: messaggio”
  4. Nel 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