20 Delegados
20.1 Geral
Uma declaração delegada define uma classe derivada da classe System.Delegate
. Uma instância delegada encapsula uma lista de invocação, que é uma lista de um ou mais métodos, cada um dos quais é chamado de entidade chamável. Para métodos de instância, uma entidade chamável consiste em uma instância e um método nessa instância. Para métodos estáticos, uma entidade chamável consiste apenas em um método. Invocar uma instância delegada com um conjunto apropriado de argumentos faz com que cada uma das entidades chamáveis do delegado seja invocada com o conjunto de argumentos fornecido.
Nota: Uma propriedade interessante e útil de uma instância delegada é que ela não conhece ou se preocupa com as classes dos métodos que encapsula; tudo o que importa é que esses métodos sejam compatíveis (§20.4) com o tipo do delegado. Isso torna os delegados perfeitamente adequados para invocação "anônima". nota final
20.2 Declarações de delegados
Um delegate_declaration é um type_declaration (§14.7) que declara um novo tipo de delegado.
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier é definido no §23.2.
É um erro de tempo de compilação que o mesmo modificador apareça várias vezes em uma declaração delegada.
Uma declaração de delegado que fornece uma variant_type_parameter_list é uma declaração de delegado genérica. Além disso, qualquer delegado aninhado dentro de uma declaração de classe genérica ou uma declaração de struct genérica é em si uma declaração de delegado genérica, uma vez que os argumentos de tipo para o tipo que o contém devem ser fornecidos para criar um tipo construído (§8.4).
O new
modificador só é permitido em delegados declarados dentro de outro tipo, caso em que especifica que tal delegado oculta um membro herdado com o mesmo nome, conforme descrito em §15.3.5.
Os public
modificadores , protected
, internal
e private
controlam a acessibilidade do tipo delegado. Dependendo do contexto em que a declaração delegada ocorre, alguns desses modificadores podem não ser permitidos (§7.5.2).
O nome do tipo do delegado é identificador.
Assim como acontece com os métodos (§15.6.1), se ref
estiver presente, o delegado retornará por ref; caso contrário, se return_type for void
, o delegado não retornará nenhum valor; caso contrário, o delegado retornará por valor.
A parameter_list opcional especifica os parâmetros do delegado.
O return_type de uma declaração de delegado retorna por valor ou não retorna nenhum valor especifica o tipo do resultado, se houver, retornado pelo delegado.
O ref_return_type de uma declaração de delegado returns-by-ref especifica o tipo da variável referenciada pelo variable_reference (§9.5) retornado pelo delegado.
O variant_type_parameter_list opcional (§18.2.3) especifica os parâmetros de tipo para o próprio delegado.
O tipo de retorno de um tipo delegado deve ser void
, ou seguro de saída (§18.2.3.2).
Todos os tipos de parâmetros de um tipo delegado devem ser seguros para entrada (§18.2.3.2). Além disso, quaisquer tipos de parâmetros de saída ou de referência também devem ser seguros para a saída.
Observação: os parâmetros de saída devem ser seguros para entrada devido a restrições comuns de implementação. nota final
Além disso, cada restrição de tipo de classe, restrição de tipo de interface e restrição de parâmetro de tipo em qualquer parâmetro de tipo do delegado deve ser segura para entrada.
Os tipos de delegado em C# são equivalentes ao nome, não estruturalmente equivalentes.
Exemplo:
delegate int D1(int i, double d); delegate int D2(int c, double d);
Os tipos
D1
de delegado eD2
são dois tipos diferentes, portanto, não são intercambiáveis, apesar de suas assinaturas idênticas.exemplo de fim
Como outras declarações de tipo genéricas, os argumentos de tipo devem ser fornecidos para criar um tipo de delegado construído. Os tipos de parâmetro e o tipo de retorno de um tipo de delegado construído são criados substituindo, para cada parâmetro de tipo na declaração de delegado, o argumento de tipo correspondente do tipo de delegado construído.
A única maneira de declarar um tipo de delegado é por meio de um delegate_declaration. Cada tipo delegado é um tipo de referência derivado de System.Delegate
. Os membros necessários para cada tipo de delegado são detalhados em §20.3. Os tipos delegados são implicitamente sealed
, portanto, não é permitido derivar qualquer tipo de um tipo delegado. Também não é permitido declarar um tipo de classe não delegado derivado de System.Delegate
. System.Delegate
não é em si um tipo delegado; É um tipo de classe do qual todos os tipos de delegados são derivados.
20.3 Membros delegados
Cada tipo de delegado herda membros da classe, Delegate
conforme descrito em §15.3.4. Além disso, cada tipo de delegado deve fornecer um método não genérico Invoke
cuja lista de parâmetros corresponda ao parameter_list na declaração de delegado, cujo tipo de retorno corresponda ao return_type ou ref_return_type na declaração de delegado e para delegados retornados por ref cujo ref_kind corresponda ao da declaração de delegado. O Invoke
método deve ser pelo menos tão acessível quanto o tipo de delegado que o contém. Chamar o Invoke
método em um tipo delegado é semanticamente equivalente a usar a sintaxe de invocação delegada (§20.6) .
As implementações podem definir membros adicionais no tipo delegado.
Exceto para instanciação, qualquer operação que possa ser aplicada a uma classe ou instância de classe também pode ser aplicada a uma classe ou instância delegada, respectivamente. Em particular, é possível acessar membros do System.Delegate
tipo por meio da sintaxe usual de acesso a membros.
20.4 Compatibilidade delegada
Um método ou tipo M
de delegado será compatível com um tipo D
delegado se todos os itens a seguir forem verdadeiros:
D
eM
têm o mesmo número de parâmetros, e cada parâmetro emD
tem o mesmo modificador de parâmetro por referência que o parâmetro correspondente emM
.- Para cada parâmetro de valor, existe uma conversão de identidade (§10.2.2
M
- Para cada parâmetro por referência, o tipo de parâmetro em
D
é o mesmo que o tipo de parâmetro emM
. - Uma das seguintes afirmações é verdadeira:
-
D
eM
são sem valor. D
eM
são retornos por valor (§15.6.1, §20.2) e existe uma conversão de identidade ou referência implícita do tipo de retorno deM
para o tipo de retorno deD
.D
eM
são ambos retornados por ref, existe uma conversão de identidade entre o tipo de retorno deM
e o tipo de retorno deD
, e ambos têm o mesmo ref_kind.
-
Essa definição de compatibilidade permite a covariância no tipo de retorno e a contravariância nos tipos de parâmetros.
Exemplo:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }
Os métodos
A.M1
eB.M1
são compatíveis com os tiposD1
de delegado eD2
, pois eles têm o mesmo tipo de retorno e lista de parâmetros. Os métodosB.M2
,B.M3
, eB.M4
são incompatíveis com os tiposD1
de delegado eD2
, uma vez que eles têm diferentes tipos de retorno ou listas de parâmetros. Os métodosB.M5
eB.M6
são compatíveis com o tipoD3
delegado .exemplo de fim
Exemplo:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
O método
X.F
é compatível com o tipoPredicate<int>
delegado e o métodoX.G
é compatível com o tipoPredicate<string>
delegado .exemplo de fim
Observação: o significado intuitivo da compatibilidade de delegado é que um método é compatível com um tipo delegado se cada invocação do delegado puder ser substituída por uma invocação do método sem violar a segurança de tipo, tratando parâmetros opcionais e matrizes de parâmetros como parâmetros explícitos. Por exemplo, no seguinte código:
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }
O
Action<string>
tipo de delegado porque qualquer invocação de umAction<string>
delegado também seria uma invocação válida doSe a
Print(object value, bool prependTimestamp = false)
, por exemplo, oAction<string>
as regras desta cláusula.nota final
20.5 Instanciação de delegado
Uma instância de um delegado é criada por meio de uma expressão de criação de delegado (§12.8.17.6), uma conversão para um tipo delegado, combinação de delegados ou remoção de delegado. A instância delegada recém-criada refere-se a um ou mais de:
- O método estático referenciado no delegate_creation_expression ou
- O objeto de destino (que não pode ser
null
) e o método de instância referenciado no delegate_creation_expression ou - Outro delegado (§12.8.17.6).
Exemplo:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }
exemplo de fim
O conjunto de métodos encapsulados por uma instância delegada é chamado de lista de invocação. Quando uma instância delegada é criada a partir de um único método, ela encapsula esse método e sua lista de invocação contém apenas uma entrada. No entanto, quando duas instâncias não delegadas são combinadas, suas listas denull
invocação são concatenadas — na ordem do operando esquerdo e depois do operando direito — para formar uma nova lista de invocação, que contém duas ou mais entradas.
Quando um novo delegado é criado a partir de um único delegado, a lista de invocação resultante tem apenas uma entrada, que é o delegado de origem (§12.8.17.6).
Os delegados são combinados usando o binário +
(§12.10.5) e os operadores (+=
). Um delegado pode ser removido de uma combinação de delegados, usando o binário -
(§12.10.6) e -=
os operadores (§12.21.4). Os delegados podem ser comparados quanto à igualdade (§12.12.9).
Exemplo: o exemplo a seguir mostra a instanciação de vários delegados e suas listas de invocação correspondentes:
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }
Quando
cd1
ecd2
são instanciados, cada um deles encapsula um método. Quandocd3
é instanciado, ele tem uma lista de invocação de dois métodosM1
eM2
, nessa ordem.cd4
A lista de invocação da contémM1
,M2
, eM1
, nessa ordem. Paracd5
, a lista de invocação contémM1
,M2
,M1
,M1
eM2
, nessa ordem.Ao criar um delegado de outro delegado com um delegate_creation_expression o resultado tem uma lista de invocação com uma estrutura diferente da original, mas que resulta nos mesmos métodos sendo invocados na mesma ordem. When
td3
é criado a partir de sua lista decd3
invocação tem apenas um membro, mas esse membro é uma lista dos métodosM1
eM2
esses métodos são invocados portd3
na mesma ordem em que são invocados porcd3
. Da mesma forma, quandotd4
é instanciado, sua lista de invocação tem apenas duas entradas, mas invoca os três métodosM1
, , eM2
, nessa ordem,M1
assim comocd4
faz.A estrutura da lista de invocação afeta a subtração do delegado. Delegado
cd6
, criado subtraindocd2
(que invocaM2
) decd4
(que invocaM1
,M2
, eM1
) invocaM1
eM1
. No entanto, delegartd6
, criado subtraindocd2
(que invocaM2
) detd4
(que invocaM1
,M2
, eM1
) ainda invocaM1
,M2
eM1
, nessa ordem, comoM2
não é uma única entrada na lista, mas um membro de uma lista aninhada. Para obter mais exemplos de combinação (bem como remoção) de delegados, consulte §20.6.exemplo de fim
Depois de instanciada, uma instância delegada sempre se refere à mesma lista de invocação.
Observação: lembre-se de que, quando dois delegados são combinados ou um é removido do outro, um novo delegado resulta com sua própria lista de chamadas; as listas de chamadas dos delegados combinados ou removidos permanecem inalteradas. nota final
20.6 Delegar invocação
O C# fornece sintaxe especial para invocar um delegado. Quando uma instância de delegado nãonull
cuja lista de invocação contém uma entrada é invocada, ela invoca o método com os mesmos argumentos fornecidos e retorna o mesmo valor que o método referenciado. (Consulte §12.8.10.4 para obter informações detalhadas sobre invocação de delegado.) Se ocorrer uma exceção durante a invocação de tal delegado e essa exceção não for capturada dentro do método que foi invocado, a pesquisa de uma cláusula de captura de exceção continuará no método que chamou o delegado, como se esse método tivesse chamado diretamente o método ao qual aquele delegado se referia.
A invocação de uma instância delegada cuja lista de invocação contém várias entradas prossegue invocando cada um dos métodos na lista de invocação de forma síncrona, em ordem. Cada método assim chamado recebe o mesmo conjunto de argumentos que foi dado à instância delegada. Se essa invocação delegada incluir parâmetros de referência (§15.6.2.3.3), cada invocação de método ocorrerá com uma referência à mesma variável; as alterações nessa variável por um método na lista de invocação serão visíveis para métodos mais abaixo na lista de invocação. Se a invocação do delegado incluir parâmetros de saída ou um valor retornado, seu valor final virá da invocação do último delegado na lista. Se ocorrer uma exceção durante o processamento da invocação de tal delegado e essa exceção não for capturada no método que foi invocado, a pesquisa por uma cláusula catch de exceção continuará no método que chamou o delegado e todos os métodos mais abaixo na lista de invocação não serão invocados.
A tentativa de invocar uma instância delegada cujo valor é null
resulta em uma exceção do tipo System.NullReferenceException
.
Exemplo: o exemplo a seguir mostra como instanciar, combinar, remover e invocar delegados:
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }
Conforme mostrado na instrução
cd3 += cd1;
, um delegado pode estar presente em uma lista de invocação várias vezes. Nesse caso, ele é simplesmente invocado uma vez por ocorrência. Em uma lista de invocação como essa, quando esse delegado é removido, a última ocorrência na lista de invocação é a que realmente foi removida.Imediatamente antes da execução da instrução final,
cd3 -= cd1
;, o delegadocd3
se refere a uma lista de invocação vazia. Tentar remover um delegado de uma lista vazia (ou remover um delegado inexistente de uma lista não vazia) não é um erro.A saída produzida é:
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60
exemplo de fim
ECMA C# draft specification