Condividi tramite


Uso dei delegati (Guida per programmatori C#)

Un delegato è un tipo che incapsula in modo sicuro un metodo, simile a un puntatore a funzione in C e C++. A differenza dei puntatori a funzione C, i delegati sono orientati agli oggetti, indipendenti dai tipi e sicuri. Nell'esempio seguente viene dichiarato un delegato denominato Callback che può incapsulare un metodo che accetta una stringa come argomento e restituisce void:

public delegate void Callback(string message);

Un oggetto delegato viene in genere costruito fornendo il nome del metodo a cui il delegato esegue il wrapping o con un'espressione lambda. Un delegato può essere richiamato una volta creata un'istanza in questo modo. Richiamare un delegato chiama il metodo associato all'istanza del delegato. I parametri passati al delegato dal chiamante vengono passati al metodo . Il delegato restituisce il valore di ritorno, se presente, dal metodo. Per esempio:

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

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

I tipi delegati sono derivati dalla Delegate classe in .NET. I tipi delegati sono sealed, non possono essere derivati da e non è possibile derivare classi personalizzate da Delegate. Poiché il delegato istanziato è un oggetto, può essere passato come argomento o assegnato a una proprietà. Un metodo può accettare un delegato come parametro e chiamare il delegato in un secondo momento. Questo metodo è noto come callback asincrono ed è un metodo comune per notificare a un chiamante al termine di un processo lungo. Quando un delegato viene usato in questo modo, il codice che usa il delegato non richiede alcuna conoscenza dell'implementazione del metodo in uso. La funzionalità è simile alle interfacce di incapsulamento fornite.

Un altro utilizzo comune delle funzioni di callback consiste nel definire un metodo di confronto personalizzato e passare quel delegato a un metodo di ordinamento. Consente al codice del chiamante di diventare parte dell'algoritmo di ordinamento. Il metodo di esempio seguente usa il Del tipo come parametro:

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

È quindi possibile passare il delegato creato nell'esempio precedente a tale metodo:

MethodWithCallback(1, 2, handler);

E ricevere l'output seguente nella console:

The number is: 3

MethodWithCallback non è necessario chiamare direttamente la console. Non deve essere progettato tenendo presente una console. Ciò che MethodWithCallback fa è preparare una stringa e passare la stringa a un altro metodo. Un metodo delegato può usare un numero qualsiasi di parametri.

Quando un delegato viene costruito per eseguire il wrapping di un metodo di istanza, il delegato fa riferimento sia all'istanza che al metodo . Un delegato non ha alcuna conoscenza del tipo di istanza oltre al metodo che incapsula. Un delegato può fare riferimento a qualsiasi tipo di oggetto, purché sia presente un metodo su tale oggetto che corrisponde alla firma del delegato. Quando un delegato viene costruito per eseguire il wrapping di un metodo statico, fa riferimento solo al metodo . Si considerino le dichiarazioni seguenti:

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

Insieme all'oggetto statico DelegateMethod illustrato in precedenza, sono ora disponibili tre metodi che è possibile incapsulare in un'istanza Del.

Un delegato può chiamare più di un metodo quando viene richiamato, un processo noto come multicasting. Per aggiungere un metodo aggiuntivo all'elenco di metodi del delegato, ovvero la lista di invocazione, è sufficiente aggiungere due delegati usando gli operatori di addizione o di assegnazione con addizione ('+' o '+='). Per esempio:

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

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

allMethodsDelegate contiene tre metodi nella lista di invocazione: Method1, Method2, e DelegateMethod. I tre delegati originali, d1, d2e d3, rimangono invariati. Quando allMethodsDelegate viene richiamato, tutti e tre i metodi vengono chiamati in ordine. Se il delegato usa parametri di riferimento, il riferimento viene passato in sequenza a ognuno dei tre metodi a sua volta e tutte le modifiche apportate da un metodo sono visibili al metodo successivo. Quando uno dei metodi genera un'eccezione che non viene intercettata all'interno del metodo , tale eccezione viene passata al chiamante del delegato. Non vengono chiamati i metodi successivi nella lista di invocazione. Se il delegato ha un valore restituito e/o i parametri out, restituisce il valore restituito e i parametri dell'ultimo metodo richiamato. Per rimuovere un metodo dall'elenco delle chiamate, usare l'operatore di sottrazione o di assegnazione di sottrazione ( o -). Per esempio:

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Callback oneMethodDelegate = (allMethodsDelegate - d2)!;

Poiché i tipi delegati sono derivati da System.Delegate, i metodi e le proprietà definiti da tale classe possono essere chiamati nel delegato. Ad esempio, per trovare il numero di metodi nell'elenco chiamate di un delegato, è possibile scrivere:

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

I delegati che hanno più di un metodo nella lista di invocazione derivano da MulticastDelegate, che è una sottoclasse di System.Delegate. Il codice precedente funziona in entrambi i casi perché entrambe le classi supportano GetInvocationList.

I delegati multicast vengono usati ampiamente nella gestione degli eventi. Gli oggetti origine evento inviano notifiche degli eventi agli oggetti destinatario registrati per ricevere tale evento. Per eseguire la registrazione per un evento, il destinatario crea un metodo progettato per gestire l'evento, quindi crea un delegato per tale metodo e passa il delegato all'origine evento. La sorgente chiama il delegato quando si verifica l'evento. Il delegato chiama quindi il metodo di gestione degli eventi sul destinatario, trasmettendo i dati dell'evento. L'origine evento definisce il tipo delegato per un determinato evento. Per altre informazioni, vedere Eventi.

Il confronto dei delegati di due tipi diversi assegnati in fase di compilazione genera un errore di compilazione. Se le istanze del delegate sono di tipo System.Delegate statico, il confronto è consentito, ma restituisce "false" in fase di esecuzione. Per esempio:

delegate void Callback1();
delegate void Callback2();

static void method(Callback1 d, Callback2 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.
    Console.WriteLine(d == f);
}

Vedere anche