Delegate ed Eventi

.::Home::.

.::Introduzione::.

1.Panoramica

4.DataBase

.::Presentazioni PPT::.

.::Link::.

.::DownLoad::.

 

Un delegate in C# è uno strumento per ottenere qualcosa che i programmatori di C o C++ dovrebbero conoscere come callback.

Una delle caratteristiche fondamentali del CLR è la possibilità per un'applicazione di registrare una funzione di richiamo (callback) con un componente ottenendo che il componente invochi questa funzione quando avviene qualche evento speciale.

Ad esempio si vorrebbe poter registrare una funzione di callback che venga chiamata quando un utente seleziona una voce di menù particolare, o quando è elevata una eccezione non gestita in altro modo, o ancora quando una operazione asincrona completa la sua esecuzione.

Nel Runtime, questi meccanismi e altri ancora sono gestiti utilizzando i delegate.

I delegate sono tipi di riferimento che derivano dalla classe System.Delegate.

Questa registrazione avviene nel modo seguente: l'applicazione fornisce l'indirizzo di un metodo che deve essere chiamato. La notifica avviene chiamando il metodo così segnalato.

Quando il componente invoca questa funzione c'è un callback fino all'applicazione.

Questo meccanismo può essere implementato solo in un linguaggio che supporta il concetto di "function pointer", in altre parole ci deve essere un meccanismo che in runtime permetta di specificare l'indirizzo di porzioni di codice in memoria che deve essere richiamato dal componente.

Perciò si potrebbe pensare ai delegate come al corrispondente dei function pointer presenti in C++ e in altri linguaggi. Tuttavia, contrariamente ai function pointer, i delegate in C# sono object oriented, type safe e sicuri; inoltre mentre un function pointer può essere utilizzato solo per far riferimento ad una static function, un delegate può fare riferimento sia ad uno static method che ad un instance method.

C# fornisce attraverso i delegate un meccanismo type safe per implementare i callback, ossia il CLR garantisce che un delegate punti sempre ad un metodo valido.

La keyword "delegate" è utilizzata quando si dichiara un metodo che rappresenta il prototipo per i metodi che verranno richiamati. Ciò è necessario in quanto il return type e i parametri del metodo di callback in C# devono essere definiti durante la compilazione per evitare gli errori in runtime che possono capitare quando si usano i function pointer in C.

Con i delegate si ottengono i vantaggi dei function pointer senza averne però anche i rischi.

I delegate sono definiti durante il runtime e si riferiscono solo ad un singolo metodo e non a classi. Hanno due scopi principali: il callback al quale abbiamo accennato e la gestione degli eventi.

Un istanza di un delegate incapsula uno o più metodi, ognuno noto come callable entity, l’oggetto delegate può essere passato al codice che richiama il metodo in esso incapsulato senza sapere in compile time quale metodo sarà invocato.
L’unica cosa che richiede al metodo che incapsula è di essere compatibile con il tipo del delegate (stessi parametri in ingresso, stesso return type).

E' possibile che una porzione di codice esegua un delegate senza sapere che metodo incapsula (e, di fatto, che metodo sarà eseguito) in quanto l'incapsulazione avviene in runtime da parte di un'altra porzione di codice.

Un esempio di uso tipico dei Callback è quello durante l'elaborazione asincrona:

Una funzione client esegue una chiamata e passa il metodo di callback alla funzione chiamata. La funzione chiamata inizia un thread e ritorna immediatamente. Quando la funzione chiamata termina l'elaborazione allora richiama la funzione di callback.

Il beneficio di questa soluzione è che la funzione client continua la sua elaborazione, senza dover attendere una chiamata sincrona che potrebbe essere anche molto lunga.

In C# una classe pubblica eventi che può far avvenire in modo che qualunque classe possa sottoscriverli. Quando ha luogo un evento il runtime si preoccupa di informare tutte le classi sottoscritte dell'occorrenza. Il metodo che viene chiamato come risultato di un evento accaduto, è definito usando i delegate.

Si chiama multicasting, la tecnica che permette ad un singolo delegate di invocare più metodi alla volta.

Quando si definisce un nuovo delegate, il compilatore genera una classe che deriva da System.MulticastDelegate (una sottoclasse di System.Delegate).

Gli eventi sono proprio un tipo speciale di delegate multicast.

I delegate possono anche essere concatenati in una lista linkata, attraverso metodi appositamente forniti come Combine che combina due liste o Remove che rimuove un delegate dalla lista.

In linea generale per utilizzare un delegate sono necessarie tre operazioni: la dichiarazione, l'istanza e la chiamata come si vedere in questo semplice esempio

// Parte I: dichiara un delegate SimpleDelegate

delegate void SimpleDelegate();

class Test
{
  //il metodo A sarà richiamato attraverso il delegate d

	static void A() {
		System.Console.WriteLine("Test.A");
	}
	static void Main() {
      // Parte II: istanzia un delegate d di A
		SimpleDelegate d = new SimpleDelegate(A);
      // Parte III: esegui d (quindi A)
		d();
	}
}

Ovviamente in questo caso si faceva prima a richiamare direttamente d, ma è solo un esempio, non bisogna dimenticare il contesto di utilizzo dei delegate che è stato appena illustrato. Da notare come il delegate e il metodo da esso rappresentato siano dello stesso tipo void.

Il codice commentato che segue vuole invece illustrare un uso di delegate e eventi, come meccanismi di callback type safe.

L'esempio dichiara un server "chat" al quale più "client" si possono collegare. Ogni client registra un delegate con il server. Quindi quando un client invia un messaggio (stringa) al server, questi inoltra il messaggio a tutti i client registrati.

Si noti come l'esempio sia diviso in due parti, la prima parte utilizza i delegate per mostrare il meccanismo del callback a basso livello, mentre la seconda implementazione utilizza gli eventi per mostrare come questi permettano di nascondere gran parte dei meccanismi di basso livello legati invece ai delegate.

Il programma presenta due classi, DChatServer che accoglie i client che registrano i loro metodi ed invia ad essi i messaggi e DChatClient che richiede la connessione e visualizza i messaggi.
Il tutto è ripetuto nella versione "E" che realizza le stesse operazioni con gli Eventi anzichè coi Delegate.
E' bene fare attenzione che i termini "registrazione" e "connessione" sono indicativi della logica di funzionamento! Non è un programma remoto e le classi Client e Server sono addirittura parte dello stesso assembly.

All'esecuzione iniziale del metodo DelegateChatServerDemo sono create tre istanze di DChatClient [1,2,3].
DChatClient espone un metodo onMsgArrived che visualizza su schermo la stringa passata a parametro e restituisce void.
Questo metodo sarà rappresentato da un delegate OnMsgArrived (con la "O" maiuscola!), che dovrà essere anche esso in grado di ricevere una stringa in ingresso e restituire void, per rispettare il vincolo di rappresentanza dei Delegate espresso in precedenza (stessi parametro di ingresso e stesso Return Type del metodo che rappresenta).
Il client registra il suo metodo onMsgArrived passando a DchatServer.ClientConnect un delegate che lo incapsula [new DChatServer.OnMsgArrived(onMsgArrived)].
DChatServer.ClientConnect aggiunge questo delegate al delegate multicast DChatServer.onMsgArrived che rappresenta di fatto la lista dei metodi registrati dai client e quindi anche la lista dei client che riceveranno i messaggi.

Quando un messaggio è inviato ai client con SendMsg i metodi onMsgArrived dei client rappresentati dai delegate in DChatServer.onMsgArrived sono eseguiti con il messaggio "msg" come ingresso, sfruttando la capacità dei multicast di eseguire più metodi con una sola chiamata [onMsgArrived(msg)]. Se un client è escluso dal messaggio, la lista dei delegate verrà scorsa e saranno invocati tutti i metodi tranne quello escluso.

Infine i client vanno disconnessi e rimossi dalla memoria.

La procedura con gli eventi è identica, tranne per la fase di registrazione, che per la stessa natura degli eventi, non è necessaria ( o meglio è compiuta in maniera più diretta con += e -=).

using System;


///////////////////////////////////////////////////////////////////////////////

// Dichiara la classe del Server basato sull'uso dei delegate
class DChatServer {
    // Dichiara un delegate di tipo multicast 
    public delegate void OnMsgArrived(String message);

    private static OnMsgArrived onMsgArrived;

    // Privato per evitare che istanze di questo type vengano istanziate
    private DChatServer() {}

    // Questa funzione č neccessaria perché non si sta usando un evento
    public static void ClientConnect(OnMsgArrived onMsgArrived) {
        DChatServer.onMsgArrived = (OnMsgArrived)
            Delegate.Combine(DChatServer.onMsgArrived, onMsgArrived);
    }

    // Questa funzione č neccessaria perché non si sta usando un evento
    public static void ClientDisconnect(OnMsgArrived onMsgArrived) {
        DChatServer.onMsgArrived = (OnMsgArrived)
            Delegate.Remove(DChatServer.onMsgArrived, onMsgArrived);
    }

    public static void SendMsg(String msg) {
        // Invia un messaggio a TUTTI i client
        SendMsg(msg, null);
    }

    public static void SendMsg(String msg, Object excludeClient) {
        // Invia un messaggio a tutti i client tranne 'excludeClient'
        if (excludeClient == null) {
            onMsgArrived(msg);
        } else {
            Delegate[] DelegateList = onMsgArrived.GetInvocationList();
            for (int i = 0; i < DelegateList.Length; i++) {
                if (DelegateList[i].Target != excludeClient) {
                    ((OnMsgArrived) DelegateList[i])(msg);
                }
            }            
        }        
    }    
}


///////////////////////////////////////////////////////////////////////////////

// Dichiara la classe del Client basato sull'uso dei delegate
class DChatClient {
    private void onMsgArrived(String msg) {
        Console.WriteLine("Msg arrived (Client {0}): {1}", clientName, msg);
    }

    private String clientName;

    public DChatClient(String clientName) {
        this.clientName = clientName;
        DChatServer.ClientConnect(new DChatServer.OnMsgArrived(onMsgArrived));
    }

    public void Dispose() {
        DChatServer.ClientDisconnect(new DChatServer.OnMsgArrived(onMsgArrived));
        GC.SuppressFinalize(this);
    }

    ~DChatClient() {
        Dispose();        
    }
}


///////////////////////////////////////////////////////////////////////////////

// Dichiara la classe del Server basato sull'uso degli eventi
class EChatServer {
    // Dichiara un delegate di tipo multicast (lo si riconosce dal void come return type)
    public delegate void OnMsgArrived(String message);


    // Dichiarare un evento causa la generazione da parte del compilatore di 
    // un campo PRIVATE (onMsgArrived) che fa riferimento alla coda di un 
    // lista linkata di delegate di OnMsgArrived. Il compilatore genera inoltre 
    // due metodi PUBLIC, add_onMsgArrived e remove_onMsgArrived che sono 
    // chiamati quando gli operatori += e -= sono applicati al delegate 
    // dell'evento
    public static event OnMsgArrived onMsgArrived;

    // Privato per evitare che istanze di questo type vengano istanziate
    private EChatServer() {}

    public static void SendMsg(String msg) {
        // Invia un messaggio a TUTTI i client
        SendMsg(msg, null);
    }

    public static void SendMsg(String msg, Object excludeClient) {
        // Invia un messaggio a tutti i client tranne 'excludeClient'
        if (excludeClient == null) {
            onMsgArrived(msg);
        } else {
            Delegate[] DelegateList = onMsgArrived.GetInvocationList();
            for (int i = 0; i < DelegateList.Length; i++) {
                if (DelegateList[i].Target != excludeClient) {
                    ((OnMsgArrived) DelegateList[i])(msg);
                }
            }            
        }        
    }    
}


///////////////////////////////////////////////////////////////////////////////

// Dichiara la classe del Client basato sull'uso degli eventi
class EChatClient {
    private void onMsgArrived(String msg) {
        Console.WriteLine("Msg arrived (Client {0}): {1}", clientName, msg);
    }

    private String clientName;

    public EChatClient(String clientName) {
        this.clientName = clientName;
        EChatServer.onMsgArrived += new EChatServer.OnMsgArrived(onMsgArrived);
    }

    public void Dispose() {
        EChatServer.onMsgArrived -= new EChatServer.OnMsgArrived(onMsgArrived);
        GC.SuppressFinalize(this);
    }

    ~EChatClient() {
        Dispose();        
    }
}


///////////////////////////////////////////////////////////////////////////////


class Application {
    private static void DelegateChatServerDemo() {
        Console.WriteLine("Demo start: Delegate Chat Server.");

        DChatClient cc1 = new DChatClient("1");
        DChatClient cc2 = new DChatClient("2");
        DChatClient cc3 = new DChatClient("3");

        DChatServer.SendMsg("Hi to all clients");
        DChatServer.SendMsg("Hi to all clients except client 2", cc2);


        // Sconnette esplicitamente i client dal chat server.
        // Se questo non viene fatto, la memoria dei client potrebbe
        // non essere liberata fincheč il server attivo, ovvero fino
        // alla chiusura dell'applicazione

        cc1.Dispose();
        cc2.Dispose();
        cc3.Dispose();
        Console.WriteLine("Demo stop: Delegate Chat Server.");
    }

    private static void EventChatServerDemo() {
        Console.WriteLine("\n\nDemo start: Event Chat Server.");
        EChatClient cc1 = new EChatClient("1");
        EChatClient cc2 = new EChatClient("2");
        EChatClient cc3 = new EChatClient("3");

        EChatServer.SendMsg("Hi to all clients");
        EChatServer.SendMsg("Hi to all clients except client 2", cc2);

        // Sconnette esplicitamente i client dal chat server.
        // Se questo non viene fatto, la memoria dei client potrebbe
        // non essere liberata fincheč il server attivo, ovvero fino
        // alla chiusura dell'applicazione
        cc1.Dispose();
        cc2.Dispose();
        cc3.Dispose();
        Console.WriteLine("Demo stop: Event Chat Server.");
    }

    public static void Main() {
        DelegateChatServerDemo();
        EventChatServerDemo();
    }
}

Fonti:

Documentazione .NET Framework SDK

http://www.ciol.com/content/technology/csharptutor/101103101.asp

http://www.codeguru.com/cs_delegates/AS-Delegates.html

http://www.devx.com/premier/mgznarch/vbpj/2001/09sep01/ce0109/ce0109-4.asp

http://msdn.microsoft.com/msdnmag/issues/01/06/net/net0106.asp

 

.::^top^::.

     
(2002) A cura di Carlo Becchi