Condividi tramite


Utilizzo di delegati (Guida per programmatori C#)

Aggiornamento: novembre 2007

Un delegato è un tipo in cui è incapsulato un metodo in modo sicuro. È simile a un puntatore a una funzione in C e C++ ma, diversamente da questo, è orientato ad oggetti, indipendente dai tipi e protetto. Il tipo di un delegato è definito dal nome del delegato stesso. Nell'esempio riportato di seguito viene dichiarato un metodo delegato, Del, in grado di incapsulare un metodo che accetta una stringa come argomento e restituisce void:

public delegate void Del(string message);

Per la costruzione di un oggetto delegato è in genere sufficiente fornire il nome del metodo di cui il delegato eseguirà il wrapping oppure utilizzare un metodo anonimo. Una volta creata un'istanza di un delegato, quest'ultimo passerà al metodo le chiamate ricevute. I parametri passati al delegato dal chiamante verranno passati al metodo e l'eventuale valore restituito dal metodo verrà restituito dal delegato al chiamante. Per fare riferimento a questo processo si afferma normalmente che il delegato viene richiamato. È possibile richiamare un delegato di cui è stata creata un'istanza in modo analogo al metodo di cui è stato eseguito il wrapping dal delegato stesso. Esempio:

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

I tipi delegati vengono derivati dalla classe Delegate in .NET Framework e sono sealed, ossia non possono essere utilizzati per ulteriori derivazioni. Non è inoltre possibile derivare classi personalizzate da Delegate. Poiché il delegato di cui è stata creata un'istanza è un oggetto, può essere passato come parametro o assegnato a una proprietà. In questo modo un metodo può accettare un delegato come parametro e chiamarlo in un secondo momento. Questa operazione è nota come callback asincrono e rappresenta uno dei modi più comuni per notificare a un chiamante il completamento di un processo prolungato. Quando si utilizza un delegato in questo modo, nel codice che include il delegato non deve essere necessariamente specificata l'implementazione del metodo. La funzionalità è simile all'incapsulamento fornito dalle interfacce. Per ulteriori informazioni, vedere Quando utilizzare i delegati anziché le interfacce.

Un altro utilizzo comune dei callback consiste nel definire un metodo di confronto personalizzato e nel passare il delegato a un metodo di ordinamento. In questo modo il codice del chiamante diventa parte integrante dell'algoritmo di ordinamento. Nel metodo di esempio riportato di seguito il tipo Del viene utilizzato come parametro:

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

È quindi possibile passare a tale metodo il delegato creato in precedenza:

MethodWithCallback(1, 2, handler);

e ricevere il seguente output sulla console:

The number is: 3

Poiché MethodWithCallback utilizza il delegato come astrazione, non deve chiamare direttamente la console, né deve essere progettato tenendo presente una console. MethodWithCallback si limita a preparare una stringa e a passarla a un altro metodo. Questo comportamento è particolarmente efficace perché un metodo delegato può utilizzare un numero qualsiasi di parametri.

Quando viene costruito in modo da eseguire il wrapping di un metodo di istanza, il delegato fa riferimento sia all'istanza che al metodo. Poiché non conosce il tipo dell'istanza, a parte il metodo di cui esegue il wrapping, un delegato può fare riferimento a qualsiasi tipo di oggetto, purché per tale oggetto sia disponibile un metodo che corrisponde alla firma del delegato. Quando viene costruito in modo da eseguire il wrapping di un metodo statico, il delegato fa riferimento solo al metodo. Si considerino le seguenti dichiarazioni:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

Insieme al metodo statico DelegateMethod illustrato in precedenza, sono ora presenti tre metodi di cui può essere eseguito il wrapping da parte di un'istanza di Del.

Quando richiamato, un delegato può chiamare più metodi. Questo processo è definito multicasting. Per aggiungere un metodo all'elenco dei metodi del delegato, ossia l'elenco delle chiamate, è sufficiente aggiungere due delegati utilizzando l'operatore di addizione (+) oppure l'operatore di assegnazione di addizione (+=). Esempio:

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

A questo punto nell'elenco delle chiamate a allMethodsDelegate sono inclusi tre metodi: Method1, Method2 e DelegateMethod. I tre delegati originali, d1, d2 e d3, rimangono invariati. Quando si richiama allMethodsDelegate, vengono chiamati nell'ordine tutti e tre i metodi. Se il delegato utilizza parametri di riferimento, il riferimento viene passato in sequenza a ciascuno dei tre metodi e le eventuali modifiche apportate da un metodo risulteranno visibili a quello successivo. Un'eventuale eccezione generata da uno qualsiasi dei metodi, e non rilevata all'interno del metodo, verrà passata al chiamante del delegato e i metodi successivi nell'elenco non verranno chiamati. Se per il delegato sono definiti un valore restituito e/o parametri out, verranno restituiti il valore restituito e i parametri dell'ultimo metodo chiamato. Per rimuovere un metodo dall'elenco, utilizzare l'operatore di decremento (-) oppure l'operatore di assegnazione di decremento (-=). Esempio:

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

Poiché i tipi di delegato vengono derivati da System.Delegate, nel delegato possono essere chiamati i metodi e le proprietà definiti da tale classe. Per trovare, ad esempio, il numero di metodi inclusi nell'elenco delle chiamate a un delegato, è possibile scrivere:

int invocationCount = d1.GetInvocationList().GetLength(0);

I delegati che includono più metodi nell'elenco derivano da MulticastDelegate, che è una sottoclasse di System.Delegate. Il codice sopra riportato funziona in entrambi i casi poiché tutte e due le classi supportano GetInvocationList.

I delegati multicast vengono ampiamente utilizzati nella gestione degli eventi. Gli oggetti origine inviano le notifiche relative a un evento agli oggetti destinatario registrati per la ricezione di tale evento. Per eseguire la registrazione per un evento, il destinatario crea un apposito metodo per la gestione dell'evento, quindi crea un delegato per tale metodo e lo passa all'origine evento. Quando l'evento si verifica, l'origine chiama il delegato, che a sua volta chiama il metodo di gestione degli eventi sul destinatario, recapitando i dati relativi all'evento. Il tipo di delegato per un evento specifico è definito dall'origine evento. Per ulteriori informazioni, vedere Eventi (Guida per programmatori C#).

Se si confrontano delegati di due tipi diversi assegnati in fase di compilazione, verrà generato un errore di compilazione. Se le istanze dei delegati sono staticamente del tipo System.Delegate, il confronto è consentito, ma verrà restituito false in fase di esecuzione. Esempio:

delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f 
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}

Vedere anche

Concetti

Guida per programmatori C#

Riferimenti

Delegati (Guida per programmatori C#)

Covariante e controvariante nei delegati (Guida per programmatori C#)

Eventi (Guida per programmatori C#)