Usando delegados (Guia de Programação em C#)

Um delegado é um tipo que encapsula com segurança um método, semelhante a um ponteiro de função em C e C++. No entanto, ao contrário dos ponteiros de função de C, delegados são orientados a objeto, fortemente tipados e seguros. O tipo de um delegado é definido pelo nome do delegado. O exemplo a seguir declara um delegado chamado Callback que pode encapsular um método que usa uma cadeia de caracteres como um argumento e retorna nulo:

public delegate void Callback(string message);

Normalmente, um objeto delegado é construído fornecendo-se o nome do método que o delegado encapsulará ou com uma expressão lambda. Depois que um representante é instanciado dessa maneira, ele pode ser invocado. A invocação de um representante chama o método anexado à instância delegada. Os parâmetros passados para o delegado pelo chamador são passados para o método e o valor de retorno, se houver, do método é retornado ao chamador pelo delegado. Por exemplo:

// 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");

Tipos de delegado são derivados da classe Delegate no .NET. Tipos de delegado são lacrados – não podem ser derivados de – e não é possível derivar classes personalizadas de Delegate. Como o delegado instanciado é um objeto, ele pode ser passado como um argumento ou atribuído a uma propriedade. Isso permite que um método aceite um delegado como um parâmetro e chame o delegado posteriormente. Isso é conhecido como um retorno de chamada assíncrono e é um método comum de notificação de um chamador quando um processo longo for concluído. Quando um delegado é usado dessa maneira, o código que usa o delegado não precisa de conhecimento algum da implementação do método que está sendo usado. A funcionalidade é semelhante ao encapsulamento que as interfaces fornecem.

Outro uso comum de chamadas de retorno é definir um método de comparação personalizada e passar esse delegado para um método de classificação. Ele permite que o código do chamador se torne parte do algoritmo de classificação. O método de exemplo a seguir usa o tipo Del como um parâmetro:

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

Em seguida, você pode passar o delegado criado acima para esse método:

MethodWithCallback(1, 2, handler);

e receber a seguinte saída para o console:

The number is: 3

Usando o delegado como uma abstração, MethodWithCallback não precisa chamar o console diretamente — ele não precisa ser criado com um console em mente. O que MethodWithCallback faz é simplesmente preparar uma cadeia de caracteres e passá-la para outro método. Isso é especialmente poderoso, uma vez que um método delegado pode usar qualquer número de parâmetros.

Quando um delegado é construído para encapsular um método de instância, o delegado faz referência à instância e ao método. Um delegado não tem conhecimento do tipo de instância além do método que ele encapsula, de modo que um delegado pode se referir a qualquer tipo de objeto desde que haja um método nesse objeto que corresponda à assinatura do delegado. Quando um delegado é construído para encapsular um método estático, ele só faz referência ao método. Considere as seguintes declarações:

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

Além do DelegateMethod estático mostrado anteriormente, agora temos três métodos que podem ser encapsulados por uma instância Del.

Um delegado pode chamar mais de um método quando invocado. Isso é chamado de multicast. Para adicionar um método extra à lista de métodos do delegado — a lista de invocação — basta adicionar dois delegados usando os operadores de adição ou de atribuição de adição ('+' ou '+ ='). Por exemplo:

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;

Nesse ponto, allMethodsDelegate contém três métodos em sua lista de invocação —Method1, Method2 e DelegateMethod. Os três delegados originais, d1, d2 e d3, permanecem inalterados. Quando allMethodsDelegate é invocado, os três métodos são chamados na ordem. Se o delegado usar parâmetros de referência, a referência será passada em sequência para cada um dos três métodos por vez, e quaisquer alterações em um método serão visíveis no próximo método. Quando algum dos métodos gerar uma exceção que não foi detectada dentro do método, essa exceção será passada ao chamador do delegado e nenhum método subsequente na lista de invocação será chamado. Se o delegado tiver um valor de retorno e/ou parâmetros de saída, ele retornará o valor de retorno e os parâmetros do último método invocado. Para remover um método da lista de invocação, use os operadores de atribuição de subtração ou subtração (- ou -=). Por exemplo:

//remove Method1
allMethodsDelegate -= d1;

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

Como os tipos de delegados são derivados de System.Delegate, os métodos e as propriedades definidos por essa classe podem ser chamados no delegado. Por exemplo, para localizar o número de métodos na lista de invocação do delegado, é possível escrever:

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

Delegados com mais de um método em sua lista de invocação derivam de MulticastDelegate, que é uma subclasse de System.Delegate. O código acima funciona em ambos os casos, pois as classes oferecem suporte à GetInvocationList.

Delegados multicast são amplamente usados na manipulação de eventos. Objetos de origem do evento enviam notificações de eventos aos objetos de destinatário que se registraram para receber esse evento. Para se registrar para um evento, o destinatário cria um método projetado para lidar com o evento, em seguida, cria um delegado para esse método e passa o delegado para a origem do evento. A origem chama o delegado quando o evento ocorre. O delegado chama então o método de manipulação de eventos no destinatário, fornecendo os dados do evento. O tipo de delegado de um determinado evento é definido pela origem do evento. Para saber mais, consulte Eventos.

A comparação de delegados de dois tipos diferentes atribuídos no tempo de compilação resultará em um erro de compilação. Se as instâncias de delegado forem estaticamente do tipo System.Delegate, então a comparação será permitida, mas retornará false no tempo de execução. Por exemplo:

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

Confira também