Partilhar via


System.Delegate e a delegate palavra-chave

Anterior

Este artigo aborda as classes no .NET que oferecem suporte a delegados e como eles são mapeados para a palavra-chave delegate .

Definir tipos de delegados

Vamos começar com a palavra-chave 'delegar', porque é principalmente isso que você usará ao trabalhar com delegados. O código que o compilador gera quando você usa a delegate palavra-chave será mapeado para chamadas de método que invocam membros das Delegate classes and MulticastDelegate .

Você define um tipo de delegado usando sintaxe semelhante à definição de uma assinatura de método. Basta adicionar a delegate palavra-chave à definição.

Vamos continuar a usar o método List.Sort() como nosso exemplo. O primeiro passo é criar um tipo para o delegado de comparação:

// From the .NET Core library

// Define the delegate type:
public delegate int Comparison<in T>(T left, T right);

O compilador gera uma classe, derivada de System.Delegate que corresponde à assinatura usada (neste caso, um método que retorna um inteiro e tem dois argumentos). O tipo desse delegado é Comparison. O Comparison tipo de delegado é um tipo genérico. Para mais detalhes sobre genéricos, consulte aqui.

Observe que a sintaxe pode parecer como se estivesse declarando uma variável, mas na verdade está declarando um tipo. Você pode definir tipos delegados dentro de classes, diretamente dentro de namespaces ou até mesmo no namespace global.

Nota

Não é recomendável declarar tipos delegados (ou outros tipos) diretamente no namespace global.

O compilador também gera manipuladores de adição e remoção para esse novo tipo para que os clientes dessa classe possam adicionar e remover métodos da lista de invocação de uma instância. O compilador imporá que a assinatura do método que está sendo adicionado ou removido corresponde à assinatura usada ao declarar o método.

Declarar instâncias de delegados

Depois de definir o delegado, você pode criar uma instância desse tipo. Como todas as variáveis em C#, você não pode declarar instâncias delegadas diretamente em um namespace ou no namespace global.

// inside a class definition:

// Declare an instance of that type:
public Comparison<T> comparator;

O tipo da variável é Comparison<T>, o tipo de delegado definido anteriormente. O nome da variável é comparator.

Esse trecho de código acima declarou uma variável membro dentro de uma classe. Você também pode declarar variáveis delegadas que são variáveis locais ou argumentos para métodos.

Invocar delegados

Você invoca os métodos que estão na lista de invocação de um delegado chamando esse delegado. Dentro do Sort() método, o código chamará o método de comparação para determinar qual ordem colocar objetos:

int result = comparator(left, right);

Na linha acima, o código invoca o método anexado ao delegado. Você trata a variável como um nome de método e a invoca usando a sintaxe de chamada de método normal.

Essa linha de código faz uma suposição insegura: não há garantia de que um destino tenha sido adicionado ao delegado. Se nenhum alvo tiver sido anexado, a linha acima fará com que um NullReferenceException seja lançado. As expressões idiomáticas usadas para resolver esse problema são mais complicadas do que uma simples verificação nula e são abordadas mais adiante nesta série.

Atribuir, adicionar e remover destinos de invocação

É assim que um tipo de delegado é definido e como as instâncias delegadas são declaradas e invocadas.

Os desenvolvedores que desejam usar o List.Sort() método precisam definir um método cuja assinatura corresponda à definição de tipo de delegado e atribuí-lo ao delegado usado pelo método de classificação. Essa atribuição adiciona o método à lista de invocação desse objeto delegado.

Suponha que você queira classificar uma lista de cadeias de caracteres por seu comprimento. Sua função de comparação pode ser a seguinte:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

O método é declarado como um método privado. Não há problema. Talvez você não queira que esse método faça parte da sua interface pública. Ele ainda pode ser usado como o método de comparação quando anexado a um delegado. O código de chamada terá esse método anexado à lista de destino do objeto delegado e poderá acessá-lo por meio desse delegado.

Você cria essa relação passando esse método para o List.Sort() método:

phrases.Sort(CompareLength);

Observe que o nome do método é usado, sem parênteses. Usando o método como um argumento diz ao compilador para converter a referência de método em uma referência que pode ser usada como um destino de invocação delegado, e anexar esse método como um destino de invocação.

Você também poderia ter sido explícito declarando uma variável de tipo Comparison<string> e fazendo uma tarefa:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

Em usos em que o método que está sendo usado como um destino delegado é um método pequeno, é comum usar a sintaxe de expressão lambda para executar a atribuição:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

O uso de expressões lambda para destinos delegados é abordado mais detalhadamente em uma seção posterior.

O exemplo Sort() normalmente anexa um único método de destino ao delegado. No entanto, os objetos delegados oferecem suporte a listas de invocação que têm vários métodos de destino anexados a um objeto delegado.

Classes Delegate e MulticastDelegate

O suporte a idiomas descrito acima fornece os recursos e o suporte de que você normalmente precisará para trabalhar com delegados. Esses recursos são criados em duas classes no .NET Core framework: Delegate e MulticastDelegate.

A System.Delegate classe e sua única subclasse direta, System.MulticastDelegate, fornecem o suporte de estrutura para criar delegados, registrar métodos como destinos delegados e invocar todos os métodos registrados como um destino delegado.

Curiosamente, as System.Delegate classes e System.MulticastDelegate não são elas próprias tipos delegados. Eles fornecem a base para todos os tipos específicos de delegados. Esse mesmo processo de design de linguagem determinou que você não pode declarar uma classe que deriva de Delegate ou MulticastDelegate. As regras de linguagem C# proíbem-no.

Em vez disso, o compilador C# cria instâncias de uma classe derivada de quando você usa a palavra-chave da linguagem C# para declarar tipos delegados MulticastDelegate .

Esse design tem suas raízes na primeira versão do C# e do .NET. Um objetivo para a equipe de design era garantir que a linguagem impusesse segurança de tipo ao usar delegados. Isso significava garantir que os delegados fossem invocados com o tipo e o número corretos de argumentos. E que qualquer tipo de retorno foi indicado corretamente no momento da compilação. Os delegados faziam parte da versão 1.0 do .NET, que era anterior aos genéricos.

A melhor maneira de impor essa segurança de tipo era o compilador criar as classes delegadas concretas que representavam a assinatura do método que estava sendo usada.

Mesmo que você não possa criar classes derivadas diretamente, você usará os métodos definidos nessas classes. Vamos analisar os métodos mais comuns que você usará quando trabalhar com delegados.

O primeiro fato, mais importante a ser lembrado, é que cada delegado com quem você trabalha é derivado do MulticastDelegate. Um delegado multicast significa que mais de um destino de método pode ser invocado ao invocar por meio de um delegado. O projeto original considerava fazer uma distinção entre delegados, onde apenas um método de destino poderia ser anexado e invocado, e delegados, onde vários métodos de destino poderiam ser anexados e invocados. Esta distinção revelou-se menos útil na prática do que se pensava inicialmente. As duas classes diferentes já foram criadas, e estão no quadro desde o seu lançamento público inicial.

Os métodos que você mais usará com delegados são Invoke() e BeginInvoke() / EndInvoke(). Invoke() invocará todos os métodos que foram anexados a uma instância delegada específica. Como você viu acima, você normalmente invoca delegados usando a sintaxe de chamada de método na variável delegate. Como você verá mais adiante nesta série, existem padrões que funcionam diretamente com esses métodos.

Agora que você já viu a sintaxe da linguagem e as classes que dão suporte a delegados, vamos examinar como os delegados fortemente tipados são usados, criados e invocados.

Seguinte