I Delegate in C Sharp

Giorgio Borelli

Cosa sono e come funzionano i delegate in C#Un delegate in C# viene spesso descritto come un puntatore a funzione (in analogia con il C++), un ulteriore paragone e quello con le interfacce, un delegate (come le interfacce per le classi) specifica la signature (firma) del metodo che successivamente sarà invocato dai metodi degli oggetti che ne rispettano la firma.

Senza nulla togliere alle analogie che sussistono realmente tra i delegate ed i puntatori a funzioni e le interfacce, penso che per chi è alle prime, una tale definizione può generare un pò di confusione, allora come spiegare cos'è un delegate in C#?

I delegate in C# non sono altro che delle classi derivate della classe System.Delegate, per mezzo dei quali è possibile definire un metodo richiamabile da altri metodi. La comodità dei delegate risiede nel fatto che è possibile definire un metodo comune invocabile successivamente da altri metodi senza che questi sappiano l'implementazione del metodo che il delegate incorpora al suo interno. Se mi serve scrivere un metodo comune, come ad es. WriteMessage, mi torna utile avere un oggetto delegate che lo implementi e che sia richiamabile da qualsiasi altro metodo (che ne rispetti la firma) dei miei oggetti. Adesso è più facile intuire che i delegate come le interfacce rappresentano un'astrazione di un comportamento comune diffuso tra gli oggetti della mia applicazione, per l'analogia con i puntatori a funzione invece possiamo aggiungere che tramite i delegate è possibile usare i metodi come oggetti e passarli ad un'altro metodo (il delegate) che ne implementa e maschera il comportamento, inoltre i delegate rispetto ai puntatori a funzione sono più sicuri in quanto sono type-safe ed object oriented, il primo termine significa che i delegate sono tipizzati e quindi il metodo che invoca il delegate ne deve rispettare la signature (fimra) ovvero deve avere lo stesso numero e tipo di parametri e deve tornare lo stesso tipo, per il secondo termine abbiamo già detto che i delegate non sono altro che classi di C# e come tali sono definite ed instanziate come oggetti.

 

Come si dichiara un delegate

Un delegate in C# si dichiara come un qualsiasi altro metodo, quindi specificando i parametri con i loro tipi ed il tipo del valore di ritorno, per far capire al compilatore che stiamo definendo la signature di un delegate, basta aggiungere la parola chiave delegate prima della firma del metodo. Ad esempio, supponiamo di voler definire un metodo generico, che permetta di mandare scrivere un messaggio specificandone l'output (stampante, monitor, file, etc.) e che ci ritorni un valore booleano per conferma della corretta riuscita dell'operazione o meno, sfruttando i delegate, senza definirne il corpo, scriveremo:

public delegate bool WriteMessage(string message);

Così facendo abbiamo definito un metodo generico per l'invio di un messaggio, il quale potrà essere invocato da tutti quei metodi (statici e non) che ne rispettino la firma. Adesso definiamo una classe che presenta tre metodi per la scrittura di un messaggio sui diversi output, ad es. a video, su carta e su file.

class OutputMessage
{
    public bool ScreenMessage(string message)
    {
         try
         {
              //scrivo il messaggio a video
              return true;
         }
         catch
         {
              return false;
          }
    }

    public bool PrintMessage(string message)
    {
        try
        {
             //scrivo il messaggio sull'output di stampa
             return true;
        }
        catch
        {
             return false;
        }
    }

    public static bool FileMessage(string message)
    {
        try
        {
             //scrivo il messaggio su file
             return true;
         }
         catch
         {
              return false;
         }
    }
}

Quindi a seconda del metodo invocato sull'istanza del delegate WriteMessage otteremo un messaggio a video, sulla stampante o scritto su file.

 

Come instanziare un delegate

Abbiamo detto che un delegate non è altro che una classe che definisce un metodo, per instanziarlo pertanto usiamo la stessa sintassi usata per tutti gli altri oggetti, ovvero per mezzo della parola chiave new, in questo modo:

OutputMessage output = new OutputMessage();
//instanzio il primo delegate sul metodo ScreenMessage
WriteMessage msgDelegate1 = new WriteMessage(output.ScreenMessage);

//instanzio il secondo delegate sul metodo PrintMessage
WriteMessage msgDelegate2 = new WriteMessage(output.PrintMessage);

//instanzio il terzo delegate sul metodo statico FileMessage
WriteMessage msgDelegate2 = new WriteMessage(OutputMessage.FileMessage);

Per prima cosa abbiamo creato l'oggetto "output" della classe OutputMessage che presenta tre metodi di visualizzazione del messaggio che rispettano tutti la firma del delegate WriteMessage, due di questi sono metodi normali ed uno è statico. Nell'implementare i delegate usiamo la definizione della nostra classe per il metodo del delegate, ovvero WriteMessage e gli passiamo al costruttore il metodo che questo andrà a richiamare (un metodo che richiamo un'altro metodo, analogia con i puntatori a funzione), notiamo infine che per il terzo metodo non abbiamo bisogno dell'oggetto "output" ma invochiamo direttamente il metodo "FileMessage" in quanto esso è un metodo statico.

 

Come invocare il delegate

Adesso non ci resta che invocare il delegate desiderato a seconda che vogliamo stampare a video, su carta o su file, ii questo modo:

string message = "Scrivo qualcosa";
bool tuttoOK = false;
tuttoOK = msgDelegate1(message); //stampa a video
tuttoOK = msgDelegate2(message); //stampa su carta
tuttoOK = msgDelegate3(message); //stampa su file

if (tuttoOK)
    { ... }
else
    { ... }

Possiamo sfruttare ancora meglio il delegate che abbiamo definito, implementando ad esempio un metodo nella classe OutputMessage che riceva come parametro il delegate stesso e stampi conseguentemente direttamente sull'output a seconda dell'instanza del delegate passato, ottenendo di fatto un metodo polimorfico per la stampa sui vari device:

public bool PrintMessage(WriteMessage msgDelegate, string message)
{
    return msgDelegate(message);
}

Quindi a seconda dell'instanza, msgDelegate1-2-3 del delegate passato al metodo "PrintMessage", otteremo automaticamente la stampa a video, su carta o su file. Attenzione, il metodo "PrintMessage" non deve rispettare la firma del delegate (e infatti non lo fà) e ritorna un valore booleana per semplice comodità d'esempio.

I vantaggi dei delegate permettono di espandere e rendere più polimorfici i nostri oggetti, ma i loro vantaggi non finiscono qui, essi infatti i delegate permettono di definire più di un metodo al loro interno eseguendo così più metodi per l'iivocazione di un solo delegate.

 

Come definire più metodi all'interno dello stesso delegate

In questo caso parliamo di delegate multicast, i quali derivano da una classe apposita, la classe System.MulticastDelegate. Per aggiungere un'ulteriore metodo alla lista d'invocazione dei metodi di un delegate, usiamo gli operatori messici a disposizione dalla classe madre, System.MulticastDelegate, ossia l'operatore + e l'operatore +=, ad es. per far sì che l'oggetto delegate msgDelegate1 scrivi sia sul video che sulla stampa, aggiungiamo alla sua lista d'invocazione il delegate per la scrittura del messaggio su carta, in questo modo:

msgDelegate1 += new WriteMessage(output.PrintMessage);

Se invece vogliamo instanziare un nuovo oggetto che implementi i delegate per la stampa a video e su carta, possiamo usare l'operatore + in questo modo:

WriteMessage msgDelegate4 = msgDelegate1 + new WriteMessage(output.PrintMessage);

Invocando allora l'oggetto delegate msgDelegate1 o msgDelegate4 invocheremo entrambi i metodi "ScreenMessage" e "PrintMessage", precisiamo inoltre che l'ordine d'invocazione rispetta quello di definizione, quindi prima stampa a video e poi su carta.

Per ottenere la lista d'invocazione dei delegate, è possibile usare il metodo GetInvocationList, il quale torna un array di delegate in ordine d'invocazione, ad es. in questo modo:

foreach(Delegate dlg in msgDelegate1.GetInvocationList())
    dlg.Method;

La proprietà Method di ogni singolo delegate presente nel foreach, restituisce una stringa contenente il nome del metodo secondo l'ordine d'invocazione presente nel MulticastDelegate msgDelegate1.

 

Conclusioni

I delegate in C# non sono sicuramente di facile approccio, bisogna avere un po di dimestichezza con la programmazione orientata agli oggetti ed aver realizzato almeno degli esempi che aiutino a capire bene il meccanismo che sta dietro a queste classi per la definizione di metodi, ricordiamo che tramite i delegate è possibile definire oggetti che specifichino la signature di un metodo comune ed invocabile dai metodi degli altri oggetti che ne rispettano la firma, permettono anche d'invocare più di un metodo per volta tramite i delegate multicast, ma quello che dovrebbe risaltare più di tutto e che le loro caratteristiche consentono d'implementare ed ampliare il polimorfismo in C#, uno dei pilastri della programmazione orientata agli oggetti (OOP).

Se volete aggiungere precisazioni, ulteriori approfondimenti o domande sui delegate in C#, vi invito a farlo tramite i commenti, ogni vostro contributo sarà importante per l'accrescimento della comprensione dei delegate.

Categorie: C#

Tags: ,

Commenti (2) -

Mi spieghi cortesemente per quale motivo istanzi la classe e poi utilizzi il delegate, secondo me in questo caso utilizzi in modo scorretto i delegate.

Rispondi

Ciao Davide,
la classe presenta 3 metodi che rispecchiano la signature del delegate dell'esempio, di cui solo l'ultimo è statico, senza instanziare la classe posso passare al costruttore del delegate (per instanziarlo) il solo metodo statico, quindi per completezza d'esempio ho creato l'oggetto della classe, per far vedere che il delegate è implementabile sia su metodi statici che non.

Perchè, tu come faresti?

Rispondi

Pingbacks and trackbacks (1)+

Aggiungi Commento

biuquote
Loading