Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
12.1 Geral
Uma expressão é uma sequência de operadores e operandos. Essa cláusula define a sintaxe, a ordem de avaliação dos operandos e operadores, além do significado das expressões.
12.2 Classificações de expressão
12.2.1 Geral
O resultado de uma expressão é classificado como um dos seguintes:
- Um valor . Cada valor tem um tipo associado.
- Uma variável. A menos que especificado de outra forma, uma variável é tipada explicitamente e tem um tipo associado, ou seja, o tipo declarado da variável. Uma variável tipada implicitamente não tem nenhum tipo associado.
- Um literal nulo. Uma expressão com essa classificação pode ser convertida implicitamente em um tipo de referência ou tipo de valor anulável.
- Uma função anônima. Uma expressão com essa classificação pode ser convertida implicitamente em um tipo de árvore de expressão ou tipo de delegado compatível.
- Uma tupla. Cada tupla tem um número fixo de elementos; cada um com uma expressão e um nome opcional para o elemento da tupla.
- Um acesso de propriedade. Cada acesso à propriedade tem um tipo associado, ou seja, o tipo da propriedade. Além disso, um acesso de propriedade pode ter uma expressão de instância associada. Quando um acessador de acesso de propriedade de instância é invocado, o resultado da avaliação da expressão de instância se torna a instância representada por
this
(§12.8.14). - Um acesso de indexador. Cada acesso de indexador tem um tipo associado, ou seja, o tipo de elemento do indexador. Além disso, um acesso de indexador tem uma expressão de instância associada e uma lista de argumentos associada. Quando um acessador de acesso de indexador é invocado, o resultado da avaliação da expressão de instância se torna a instância representada por
this
(§12.8.14) e o resultado da avaliação da lista de argumentos se torna a lista de parâmetros da invocação. - Nada. Isso ocorre quando a expressão é uma invocação de um método com um tipo de retorno
void
. Uma expressão classificada como nada só é válida no contexto de statement_expression (§13.7) ou como o corpo de lambda_expression (§12.19).
Para expressões que ocorrem como subexpressões de expressões maiores, com as restrições anotadas, o resultado também pode ser classificado como um dos seguintes:
- Um namespace. Uma expressão com essa classificação só pode aparecer como o lado esquerdo de uma member_access (§12.8.7). Em qualquer outro contexto, uma expressão classificada como um namespace causa um erro de tempo de compilação.
- Um tipo. Uma expressão com essa classificação só pode aparecer como o lado esquerdo de uma member_access (§12.8.7). Em qualquer outro contexto, uma expressão classificada como um tipo causa um erro de tempo de compilação.
- Um grupo de métodos, que é um conjunto de métodos sobrecarregados resultante de uma pesquisa de membro (§12,5). Um grupo de métodos pode ter uma expressão de instância associada e uma lista de argumentos de tipo associado. Quando um método de instância é invocado, o resultado da avaliação da expressão de instância torna-se a instância representada por
this
(§12.8.14). Um grupo de métodos é permitido em um invocation_expression (§12.8.10) ou um delegate_creation_expression (§12.8.17.5) e pode ser convertido implicitamente em um tipo de delegado compatível (§10.8). Em qualquer outro contexto, uma expressão classificada como um grupo de métodos causa um erro de tempo de compilação. - Um acesso de evento. Cada acesso de evento tem um tipo associado, ou seja, o tipo do evento. Além disso, o acesso a um evento pode ter uma expressão de instância associada. Um acesso de evento pode aparecer como o operando esquerdo dos operadores
+=
e-=
(§12.21.5). Em qualquer outro contexto, uma expressão classificada como um acesso de evento causa um erro de tempo de compilação. Quando um acessador de acesso de evento de instância é invocado, o resultado da avaliação da expressão de instância se torna a instância representada porthis
(§12.8.14). - Uma expressão de lançamento, que pode ser utilizada em diversos contextos para lançar uma exceção dentro de uma expressão. Uma expressão throw pode ser convertida por uma conversão implícita para qualquer tipo.
Um acesso de propriedade ou acesso de indexador é sempre reclassificado como um valor pela execução de uma invocação do acessador get ou do acessador set. O acessador específico é determinado pelo contexto do acesso de propriedade ou indexador: se o acesso for o destino de uma atribuição, o acessador set será invocado para atribuir um novo valor (§12.21.2). Caso contrário, o acessador get é invocado para obter o valor atual (§12.2.2).
Um acessador de instância é um acesso de propriedade em uma instância, um acesso de evento em uma instância ou um acesso de indexador.
12.2.2 Valores de expressões
Em última análise, a maioria dos constructos que envolvem uma expressão exige a expressão para indicar um valor . Nesses casos, se a expressão real denotar um namespace, um tipo, um grupo de métodos ou nada, ocorrerá um erro de tempo de compilação. No entanto, se a expressão denotar um acesso de propriedade, um acesso de indexador ou uma variável, o valor da propriedade, do indexador ou da variável será substituído implicitamente:
- O valor de uma variável é simplesmente o valor armazenado atualmente no local de armazenamento identificado pela variável. Uma variável deve ser considerada definitivamente atribuída (§9.4) antes que seu valor possa ser obtido ou ocorrerá um erro de tempo de compilação.
- O valor de uma expressão de acesso de propriedade é obtido invocando-se o acessador get da propriedade. Se a propriedade não tiver acessador get, ocorrerá um erro de tempo de compilação. Caso contrário, uma invocação de membro de função (§12.6.6) será executada e o resultado da invocação se tornará o valor da expressão de acesso de propriedade.
- O valor de uma expressão de acesso do indexador é obtido invocando-se o acessador get do indexador. Se o indexador não tiver o acessor 'get', ocorrerá um erro em tempo de compilação. Caso contrário, uma invocação de membro de função (§12.6.6) é executada com a lista de argumentos associados à expressão de acesso do indexador e o resultado da invocação torna-se o valor da expressão de acesso do indexador.
- O valor de uma expressão de tupla é obtido aplicando-se uma conversão de tupla implícita (§10.2.13) ao tipo da expressão de tupla. É um erro obter o valor de uma expressão de tupla que não tem um tipo.
12.3 Associação estática e dinâmica
12.3.1 Geral
Associação é o processo de determinar a que uma operação se refere, com base no tipo ou valor de expressões (argumentos, operandos, receptores). Por exemplo, a associação de uma chamada de método é determinada com base no tipo do receptor e dos argumentos. A associação de um operador é determinada com base no tipo dos respectivos operandos.
Em C#, a associação de uma operação geralmente é determinada em tempo de compilação, com base no tipo de tempo de compilação das respectivas subexpressões. Da mesma forma, se uma expressão contiver um erro, o erro será detectado e relatado em tempo de compilação. Essa abordagem é conhecida como associação estática.
No entanto, se uma expressão for uma expressão dinâmica (ou seja, tiver o tipo dynamic
), isso indicará que qualquer associação da qual ela que participe deve ser baseada em seu tipo de run-time em vez do tipo que tiver em tempo de compilação. A vinculação de tal operação é, portanto, adiada até o momento em que a operação é executada durante a execução do programa. Isso é conhecido como associação dinâmica.
Quando uma operação é associada dinamicamente, pouca ou nenhuma verificação é executada no momento da compilação. Em vez disso, se a associação run-time falhar, os erros serão relatados como exceções run-time.
As seguintes operações em C# estão sujeitas à associação:
- Acesso de membro:
e.M
- Invocação de método:
e.M(e₁,...,eᵥ)
- Invocação de delegado:
e(e₁,...,eᵥ)
- Acesso de elemento:
e[e₁,...,eᵥ]
- Criação de objeto: novo
C(e₁,...,eᵥ)
- Operadores unários sobrecarregados:
+
,-
,!
(somente negação lógica),~
,++
,--
,true
,false
- Operadores binários sobrecarregados:
+
,-
,*
,/
,%
,&
,&&
,|
,||
,??
,^
,<<
,>>
,==
,!=
,>
,<
,>=
,<=
- Operadores de atribuição:
=
,= ref
,+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
,??=
- Conversões implícitas e explícitas
Quando não há expressões dinâmicas envolvidas, o C# usa como padrão a associação estática, o que significa que os tipos de subexpressões de tempo de compilação são usados no processo de seleção. No entanto, quando uma das subexpressões nas operações listadas acima é uma expressão dinâmica, a operação é vinculada dinamicamente.
Ocorrerá um erro em tempo de compilação se uma invocação de método for associada dinamicamente e qualquer um dos parâmetros, incluindo o receptor, for um parâmetro de entrada.
12.3.2 Tempo de associação
A associação estática ocorre em tempo de compilação, enquanto a associação dinâmica ocorre em run-time. Nas subcláusulas a seguir, o termo binding-time refere-se a tempo de compilação ou run-time, dependendo de quando a associação ocorrer.
Exemplo: segue abaixo as noções de associação estática e dinâmica e de tempo de associação:
object o = 5; dynamic d = 5; Console.WriteLine(5); // static binding to Console.WriteLine(int) Console.WriteLine(o); // static binding to Console.WriteLine(object) Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)
As duas primeiras chamadas são associadas estaticamente: a sobrecarga de
Console.WriteLine
é escolhida com base no tipo de tempo de compilação de seu argumento. Portanto, o tempo de associação é o tempo de compilação.A terceira chamada é dinamicamente associada: a sobrecarga de
Console.WriteLine
é escolhida com base no tipo de run-time de seu argumento. Isso acontece porque o argumento é uma expressão dinâmica – seu tipo de tempo de compilação é dinâmico. Portanto, o tempo de associação para a terceira chamada em tempo de associação é run-time.fim do exemplo
12.3.3 Associação dinâmica
Essa subcláusula é informativa.
A associação dinâmica permite que programas em C# interajam com objetos dinâmicos, ou seja, objetos que não seguem as regras normais do sistema de tipos da C#. Objetos dinâmicos podem ser objetos de outras linguagens de programação com sistemas de tipos diferentes ou podem ser objetos que são configurados programaticamente para implementar sua própria semântica de associação para operações diferentes.
O mecanismo pelo qual um objeto dinâmico implementa sua própria semântica é definido pela implementação. Uma dada interface – novamente definida pela implementação – é implementada por objetos dinâmicos para sinalizar para o run-time do C# que eles têm semântica especial. Assim, sempre que as operações em um objeto dinâmico são associadas dinamicamente, sua própria semântica de associação, em vez daquelas da C# conforme especificado aqui, assumem o controle.
Embora a finalidade da associação dinâmica seja permitir a interoperação com objetos dinâmicos, a C# permite a associação dinâmica em todos os objetos, sejam eles dinâmicos ou não. Isso permite uma integração mais suave de objetos dinâmicos, pois os resultados das operações neles nem sempre são objetos dinâmicos, mas ainda são de um tipo desconhecido para o programador em tempo de compilação. Além disso, a associação dinâmica pode ajudar a eliminar o código baseado em reflexão propenso a erros, mesmo quando nenhum objeto envolvido é objeto dinâmico.
12.3.4 Tipos de subexpressões
Quando uma operação é associada estaticamente, o tipo de uma subexpressão (por exemplo, um receptor, um argumento, um índice ou um operando) é sempre considerado o tipo de tempo de compilação dessa expressão.
Quando uma operação é associada dinamicamente, o tipo de uma subexpressão é determinado de maneiras diferentes, dependendo do tipo de tempo de compilação da subexpressão:
- Uma subexpressão de tipo dinâmico de tempo de compilação é considerada com o tipo do valor real que a expressão assume em run-time.
- Uma subexpressão cujo tipo de tempo de compilação é um parâmetro de tipo é considerada como tendo o tipo ao qual o parâmetro de tipo está associado em run-time
- Caso contrário, a subexpressão é considerada como tendo o tipo de tempo de compilação.
12.4 Operadores
12.4.1 Geral
Expressões são construídas a partir de operandos e operadores. Os operadores de uma expressão indicam quais operações devem ser aplicadas aos operandos.
Exemple: exemplos de operadores incluem
+
,-
,*
,/
enew
. Exemplos de operandos incluem literais, campos, variáveis locais e expressões. fim do exemplo
Há três tipos de operadores:
- Operadores unários. Os operadores unários usam um operando e a notação pós-fixada (como
–x
) ou a notação de sufixo (comox++
). - Operadores binários. Os operadores binários usam dois operandos e todos usam notação de infixo (como
x + y
). - Operador ternário. Existe apenas um operador ternário que é
?:
; ele usa três operandos e a notação de infixo (c ? x : y
).
A ordem de avaliação de operadores em uma expressão é determinada pela precedência e pela associatividade dos operadores (§12.4.2).
Operandos em uma expressão são avaliados da esquerda para a direita.
exemplo: em
F(i) + G(i++) * H(i)
, o métodoF
é chamado usando-se o valor antigo dei
, então o métodoG
é chamado com o valor antigo dei
e, por fim, o métodoH
é chamado com o novo valor de i. Isso é separado e não está relacionado à precedência do operador. fim do exemplo
Alguns operadores pode ser sobrecarregados. A sobrecarga de operador (§12.4.3) permite que implementações de operador definidas pelo usuário sejam especificadas para operações em que um ou os dois operandos são de um tipo struct ou de classe definida pelo usuário.
12.4.2 Precedência e associatividade dos operadores
Quando uma expressão contém vários operadores, a precedência dos operadores controla a ordem na qual os operadores individuais são avaliados.
Observação: por exemplo, a expressão
x + y * z
é avaliada comox + (y * z)
porque o operador*
tem precedência maior do que o operador binário+
. fim da observação
A precedência de um operador é estabelecida pela definição da sua produção de gramática associada.
Observação: por exemplo, uma additive_expression consiste em uma sequência de multiplicative_expressions separadas por operadores
+
ou-
, dando aos operadores+
e-
menor precedência do que os operadores*
,/
e%
. fim da observação
Observação: a seguinte tabela resume todos os operadores em ordem de precedência da mais alta para a mais baixa:
Subcláusula Categoria Operadores §12.8 Primária x.y
x?.y
f(x)
a[x]
a?[x]
x++
x--
x!
new
typeof
default
checked
unchecked
delegate
stackalloc
§12.9 Unário +
-
!x
~
++x
--x
(T)x
await x
§12.10 Multiplicativo *
/
%
§12.10 Aditiva +
-
§12.11 Deslocamento <<
>>
§12.12 Teste de tipo e relacional <
>
<=
>=
is
as
§12.12 Igualitário ==
!=
§12.13 AND lógico &
§12.13 XOR lógico ^
§12.13 OR lógico \|
§12.14 AND condicional &&
§12.14 OR condicional \|\|
§12.15 e §12.16 Coalescência nula e expressão throw ??
throw x
§12.18 Condicional ?:
§12.21 e §12.19 Atribuição e expressão lambda =
= ref
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
\|=
=>
??=
fim da observação
Quando ocorre um operando entre dois operadores com a mesma precedência, a associatividade dos operadores controla a ordem na qual as operações são executadas:
- Exceto para os operadores de atribuição e os operadores de coalescência de nulo, todos os operadores binários são associativos à esquerda, o que significa que as operações são executadas da esquerda para a direita.
Exemplo:
x + y + z
recebe uma avaliação como(x + y) + z
. fim do exemplo - Os operadores de atribuição, o operador de avaliação de nulo e o operador condicional (
?:
) são associativos à direita, o que significa que as operações são executadas da direita para a esquerda.Exemplo:
x = y = z
recebe uma avaliação comox = (y = z)
. fim do exemplo
Precedência e associatividade podem ser controladas usando parênteses.
Exemplo:
x + y * z
primeiro multiplicay
porz
e, em seguida, adiciona o resultado ax
, mas(x + y) * z
primeiro adicionax
ey
e, em seguida, multiplica o resultado porz
. fim do exemplo
12.4.3 Sobrecarga de operador
Os operadores unários e binários predefinidos têm implementações predefinidas. Além disso, as implementações definidas pelo usuário podem ser introduzidas incluindo-se as declarações de operador (§15.10) em classes e structs. As implementações de operador definidas pelo usuário sempre têm precedência sobre as implementações de operador predefinidas: somente quando não houver implementações de operador definidas pelo usuário aplicáveis, as implementações predefinidas do operador serão consideradas, conforme descrito em §12.4.4 e §12.4.5.
Os operadores unários sobrecarregáveis são:
+ - !
(somente negação lógica) ~ ++ -- true false
Observação: embora
true
efalse
não sejam usados explicitamente em expressões (e, portanto, não sejam incluídos na tabela de precedência em §12.4.2), eles são considerados operadores porque são invocados em vários contextos de expressão: expressões boolianas (§12.24) e expressões que envolvem o condicional (§12.18) e operadores lógicos condicionais (§12.14). fim da observação
Observação: o operador null-forgiving (
!
, §12.8.9 pós-fixado) não é um operador sobrecarregável. fim da observação
Os operadores binários sobrecarregados são:
+ - * / % & | ^ << >> == != > < <= >=
Somente os operadores listados acima podem ser sobrecarregados. Em particular, não é possível sobrecarregar o acesso de membro, a invocação do método ou os operadores =
, &&
, ||
, ??
, ?:
, =>
, checked
, unchecked
, new
, typeof
, default
, as
e is
.
Quando um operador binário está sobrecarregado, o operador de atribuição composta correspondente, se houver, também é implicitamente sobrecarregado.
Exemplo: uma sobrecarga do operador
*
também é uma sobrecarga do operador*=
. Isso é descrito mais adiante em §12.21. fim do exemplo
O respectivo operador de atribuição (=)
não pode ser sobrecarregado. Uma atribuição sempre realiza um armazenamento simples de um valor em uma variável (§12.21.2).
Operações de conversão, como (T)x
, são sobrecarregadas fornecendo conversões definidas pelo usuário (§10.5).
Observação: as conversões definidas pelo usuário não afetam o comportamento dos operadores
is
ouas
. fim da observação
O acesso de elemento, como a[x]
, não é considerado um operador que pode ser sobrecarregado. Em vez disso, a indexação definida pelo usuário tem suporte por meio de indexadores (§15.9).
Em expressões, os operadores são referenciados usando-se a notação de operador e, em declarações, os operadores são referenciados usando-se a notação funcional. A tabela a seguir mostra a relação entre notações de operadores e notações funcionais para operadores unários e binários. Na primeira entrada, «op» indica qualquer operador de prefixo unário sobrecarregável. Na segunda entrada, «op» indica os operadores pós-fixados unário ++
e --
. Na terceira entrada, «op» indica qualquer operador binário sobrecarregável.
Observação: por um exemplo de sobrecarregamento de
++
e--
operadores, consulte §15.10.2. fim da observação
Notação do operador | Notação funcional |
---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
As declarações de operador definidas pelo usuário sempre exigem que pelo menos um dos parâmetros que contém a declaração do operador seja do tipo de classe ou struct.
Observação: portanto, não é possível que um operador definido pelo usuário tenha a mesma assinatura que um operador predefinido. fim da observação
As declarações de operador definidas pelo usuário não podem modificar a sintaxe, a precedência nem a associatividade de um operador.
Exemplo: o operador
/
é sempre um operador binário, sempre tem o nível de precedência especificado em §12.4.2 e é sempre associativo à esquerda. fim do exemplo
Observação: embora seja possível que um operador definido pelo usuário execute qualquer atividade de computação, as implementações que produzem resultados diferentes daqueles que são intuitivamente esperados são fortemente desencorajadas. Por exemplo, uma implementação do operador
==
deve comparar os dois operandos quanto à igualdade e retornar um resultadobool
apropriado. fim da observação
As descrições de operadores individuais de §12.9 até §12.21 especificam as implementações predefinidas dos operadores e as regras adicionais que se aplicam a cada operador. As descrições usam os termos resolução de sobrecarga de operador unário, resolução de sobrecarga do operador binário, promoção numérica e definições de operador elevado, que são encontradas nas subcláusulas a seguir.
12.4.4 Resolução de sobrecarga do operador unário
Uma operação do formato «op» x
ou x «op»
, em que «op» é um operador unário sobrecarregável e x
é uma expressão do tipo X
, é processada da seguinte maneira:
- O conjunto de operadores definidos pelo usuário candidatos fornecidos por
X
para a operaçãooperator «op»(x)
é determinado usando-se as regras de §12.4.6. - Se o conjunto de operadores definidos pelo usuário candidato não estiver vazio, ele se tornará o conjunto de operadores candidatos para a operação. Caso contrário, as implementações binárias predefinidas de
operator «op»
, incluindo os formatos elevados, tornam-se o conjunto de operadores candidatos para a operação. As implementações predefinidas de um determinado operador são especificadas na descrição do operador. Os operadores predefinidos fornecidos por um tipo enum ou delegado são incluídos neste conjunto somente quando o tipo de tempo de vinculação de qualquer dos operandos—ou o tipo subjacente, caso seja um tipo anulável—é o tipo enum ou delegado. - As regras de resolução de sobrecarga de §12.6.4 são aplicadas ao conjunto de operadores de candidato para selecionar o melhor operador em relação à lista de argumentos
(x)
e esse operador se torna o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga não selecionar um único operador melhor, ocorrerá um erro de tempo de associação.
12.4.5 Resolução de sobrecarga do operador binário
Uma operação do formato x «op» y
, em que «op» é um operador binário sobrecarregável, x
é uma expressão do tipo X
e y
é uma expressão do tipo Y
, é processada da seguinte maneira:
- O conjunto de operadores definidos pelo usuário de candidato fornecidos por
X
eY
para a operaçãooperator «op»(x, y)
é determinado. O conjunto consiste na união dos operadores de candidato fornecidos porX
e os operadores de candidato fornecidos porY
, cada um determinado usando-se as regras de §12.4.6. Para o conjunto combinado, os candidatos são mesclados da seguinte maneira:- Se
X
eY
forem conversíveis de identidade ou seX
eY
forem derivados de um tipo base comum, os operadores de candidato compartilhados só ocorrerão no conjunto combinado uma vez. - Se houver uma conversão de identidade entre
X
eY
, um operador«op»Y
fornecido porY
tiver o mesmo tipo de retorno que um«op»X
fornecido porX
e os tipos de operando«op»Y
tiverem uma conversão de identidade para os tipos de operando correspondentes de«op»X
, então apenas«op»X
ocorrerá no conjunto.
- Se
- Se o conjunto de operadores definidos pelo usuário candidato não estiver vazio, ele se tornará o conjunto de operadores candidatos para a operação. Caso contrário, as implementações binárias predefinidas de
operator «op»
, incluindo os formatos elevados, tornam-se o conjunto de operadores candidatos para a operação. As implementações predefinidas de um determinado operador são especificadas na descrição do operador. Para operadores enumerados e delegados predefinidos, os únicos operadores considerados são aqueles fornecidos por um tipo de enumeração ou delegado que é o tipo de tempo de associação de um dos operandos. - As regras de resolução de sobrecarga de §12.6.4 são aplicadas ao conjunto de operadores de candidato para selecionar o melhor operador em relação à lista de argumentos
(x, y)
e esse operador se torna o resultado do processo de resolução de sobrecarga. Se a resolução de sobrecarga não selecionar um único operador melhor, ocorrerá um erro de tempo de associação.
12.4.6 Operadores de candidato definidos pelo usuário
Dado um tipo T
e uma operação operator «op»(A)
, em que «op» é um operador sobrecarregável e A
é uma lista de argumentos, o conjunto de operadores definidos pelo usuário candidato fornecido por T
para o operador «op»(A)
é determinado da seguinte maneira:
- Determine o tipo
T₀
. SeT
for um tipo de valor anulável,T₀
será seu tipo subjacente; caso contrário,T₀
será igual aT
. - Para todas as
operator «op»
declarações emT₀
e todos os formatos de precisão desses operadores, se pelo menos um operador for aplicável (§12.6.4.2) em relação à lista de argumentosA
, o conjunto de operadores candidatos consistirá em todos esses operadores aplicáveis emT₀
. - Caso contrário, se
T₀
éobject
, o conjunto de operadores candidatos estará vazio. - Caso contrário, o conjunto de operadores de candidato fornecido pelo
T₀
for o conjunto de operadores de candidato fornecido pela classe base direta deT₀
ou a classe base efetiva deT₀
seT₀
será um parâmetro de tipo.
12.4.7 Promoções numéricas
12.4.7.1 Geral
Essa subcláusula é informativa.
§12.4.7 e suas subcláusulas são um resumo do efeito combinado de:
- as regras para conversões numéricas implícitas (§10.2.3);
- as regras para melhor conversão (§12.6.4.7); e
- os operadores aritméticos disponíveis (§12.10), relacionais (§12.12) e lógicos integrais (§12.13.2).
A promoção numérica consiste em executar automaticamente algumas conversões implícitas dos operandos dos operadores numéricos unários e binários predefinidos. A promoção numérica não é um mecanismo distinto, mas sim um efeito de aplicar a resolução de sobrecarga aos operadores predefinidos. A promoção numérica especificamente não afeta a avaliação de operadores definidos pelo usuário, embora os operadores definidos pelo usuário possam ser implementados para exibir efeitos semelhantes.
Como exemplo de promoção numérica, considere as implementações predefinidas do operador *
binário:
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
Quando as regras de resolução de sobrecarga (§12.6.4) são aplicadas a esse conjunto de operadores, o efeito é selecionar o primeiro dos operadores para os quais existem conversões implícitas dos tipos de operando.
Exemplo: para a operação
b * s
, em queb
é umbyte
es
é umshort
, a resolução de sobrecarga selecionaoperator *(int, int)
como o melhor operador. Portanto, o efeito é queb
es
serão convertidos emint
e o tipo do resultado seráint
. Da mesma forma, para a operaçãoi * d
, ondei
é umint
ed
é umdouble
, a resolução deoverload
selecionaoperator *(double, double)
como o melhor operador. fim do exemplo
Fim do texto informativo.
12.4.7.2 Promoções numéricas unárias
Essa subcláusula é informativa.
A promoção numérica unária ocorre para os operandos dos operadores unários predefinidos +
, -
e ~
. A promoção numérica unária consiste na conversão de operandos do tipo sbyte
, byte
, short
, ushort
ou char
para o tipo int
. Além disso, para o operador unário –, a promoção numérica unária converte operandos do tipo uint
no tipo long
.
Fim do texto informativo.
12.4.7.3 Promoções numéricas binárias
Essa subcláusula é informativa.
A promoção numérica binária ocorre para os operandos dos operadores unários predefinidos +
, -
, *
, /
, %
, &
, |
, ^
, ==
, !=
, >
, <
, >=
e <=
. A promoção numérica binária converte implicitamente os dois operandos em um tipo comum que, no caso dos operadores não relacionais, também se torna o tipo de resultado da operação. A promoção numérica binária consiste na aplicação das seguintes regras, na ordem em que elas aparecem aqui:
- Se um dos operando for
decimal
, o outro operando será convertido para o tipodecimal
ou ocorrerá um erro de tempo de associação se o outro operando for do tipofloat
oudouble
. - Caso contrário, se qualquer operando for do tipo
double
, o outro operando será convertido para o tipodouble
. - Caso contrário, se qualquer operando for do tipo
float
, o outro operando será convertido para o tipofloat
. - Caso contrário, se um dos operandos for do tipo
ulong
, o outro operando será convertido para o tipoulong
ou ocorrerá um erro de tempo de associação se o outro operando for do tipotype sbyte
,short
,int
oulong
. - Caso contrário, se qualquer operando for do tipo
long
, o outro operando será convertido para o tipolong
. - Caso contrário, se o operando for do tipo
uint
e o outro operando for do tiposbyte
,short
ouint
, os dois operandos serão convertidos para o tipolong
. - Caso contrário, se qualquer operando for do tipo
uint
, o outro operando será convertido para o tipouint
. - Caso contrário, os dois operandos serão convertidos no tipo
int
.
Observação: a primeira regra não permite operações que misturem o tipo
decimal
com os tiposdouble
efloat
. A regra segue o fato de que não há conversões implícitas entre o tipodecimal
e os tiposdouble
efloat
. fim da observação
Observação: note também que não é possível que um operando seja do tipo
ulong
quando o outro operando for de um tipo integral assinado. O motivo é que não existe nenhum tipo integral que possa representar toda a gama deulong
, bem como os tipos integrais assinados. fim da observação
Nos dois casos acima, uma expressão de conversão pode ser usada para converter explicitamente um operando em um tipo compatível com o outro operando.
Exemplo: no código a seguir
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);
ocorre um erro de tempo de associação porque um
decimal
não pode ser multiplicado por umdouble
. O erro é resolvido convertendo-se explicitamente o segundo operando emdecimal
da seguinte maneira:decimal AddPercent(decimal x, double percent) => x * (decimal)(1.0 + percent / 100.0);
fim do exemplo
Fim do texto informativo.
12.4.8 Operadores de precisão
Operadores elevados permitem que operadores predefinidos e definidos pelo usuário que operam em tipos de valor não anuláveis também sejam usados com formatos anuláveis desses tipos. Os operadores elevados são construídos a partir de operadores predefinidos e definidos pelo usuário que satisfazem determinados requisitos, conforme descrito a seguir:
- Para os operadores unários
+
,++
,-
,--
,!
(negação lógica) e~
, um formato de precisão de um operador existirá se os tipos de operando e de resultado forem dos tipos de valor não anuláveis. O formato elevado é construído adicionando-se um único modificador?
ao operando e aos tipos de resultado. O operador elevado produzirá um valornull
se o operando fornull
. Caso contrário, o operador elevado desencapsula o operando, aplica o operador subjacente e encapsula o resultado. - Para os operadores binários
+
,-
,*
,/
,%
,&
,|
,^
,<<
e>>
, um formato de precisão de um operador existirá se os tipos de operando e de resultado forem todos dos tipos de valor não anuláveis. O formato elevado é construído adicionando-se um único modificador?
a cada operando e ao tipo de resultado. O operador elevado produzirá um valornull
se um ou ambos os operandos foremnull
(uma exceção sendo os operadores&
e|
do tipobool?
, conforme descrito em §12.13.5). Caso contrário, o operador elevado desencapsula os operandos, aplica o operador subjacente e encapsula o resultado. - Para os operadores de igualdade
==
e!=
, o formato elevado de um operador existirá se os tipos de operando forem tipos de valor não anuláveis e se o tipo de resultado forbool
. O formato elevado é construído adicionando-se um único modificador?
a cada tipo de operando. O operador elevado considera dois valoresnull
iguais e um valornull
diferente de qualquer valor nãonull
. Se os dois operandos não foremnull
, o operador elevado vai desencapsular os operandos e aplicar o operador subjacente para produzir o resultado debool
. - Para os operadores relacionais
<
,>
,<=
e>=
, existe uma forma elevada de um operador se os tipos dos operandos forem tipos de valor não anuláveis, e se o tipo de resultado forbool
. O formato elevado é construído adicionando-se um único modificador?
a cada tipo de operando. O operador elevado produzirá o valorfalse
se um ou os dois operandos foremnull
. Caso contrário, o operador elevado vai desencapsular os operandos e aplicar o operador subjacente para produzir o resultado debool
.
12.5 Pesquisa de membros
12.5.1 Geral
Uma pesquisa de membro é o processo pelo qual é determinado o significado de um nome no contexto de um tipo. Uma pesquisa de membro pode ocorrer como parte da avaliação de um simple_name (§12.8.4) ou um member_access (§12.8.7) em uma expressão. Se simple_name ou member_access ocorrer como a primary_expression de uma invocation_expression (§12.8.10.2), o membro será invocado.
Se um membro for um método ou evento, ou se for uma constante, um campo ou uma propriedade de um tipo delegado (§20) ou do tipo dynamic
(§8.2.4), o membro será considerado invocável.
A pesquisa de membro considera não apenas o nome de um membro, mas também o número de parâmetros de tipo que o membro tem e se o membro está acessível. Para fins de pesquisa de membro, os métodos genéricos e os tipos genéricos aninhados têm o número de parâmetros de tipo indicados em suas respectivas declarações e todos os outros membros têm parâmetros de tipo zero.
Uma pesquisa de um nome N
com argumentos de tipo K
em um tipo T
é processada da seguinte maneira:
- Primeiro, um conjunto de membros acessíveis denominado
N
é determinado:- Se
T
for um parâmetro de tipo, o conjunto será a união dos conjuntos de membros acessíveis denominadosN
em cada um dos tipos especificados como restrição primária ou restrição secundária (§15.2.5) paraT
, juntamente com o conjunto de membros acessíveis denominadosN
emobject
. - Caso contrário, o conjunto consiste em todos os membros acessíveis (§7.5) denominados
N
emT
, incluindo os membros herdados e os membros acessíveis denominadosN
emobject
. SeT
for um tipo construído, o conjunto de membros será obtido substituindo-se os argumentos de tipo, conforme descrito em §15.3.3. Os membros que incluem um modificador deoverride
são excluídos do conjunto.
- Se
- Em seguida, se
K
for zero, todos os tipos aninhados cujas declarações incluírem parâmetros de tipo serão removidos. SeK
não for zero, todos os membros com um número diferente de parâmetros de tipo serão removidos. QuandoK
é zero, os métodos que têm parâmetros de tipo não são removidos, uma vez que o processo de inferência de tipo (§12.6.3) pode ser capaz de inferir os argumentos de tipo. - Em seguida, se o membro for invocado, todos os membros não invocados serão removidos do conjunto.
- Em seguida, os membros ocultos por outros membros são removidos do conjunto. Para cada membro
S.M
no conjunto, em queS
é o tipo no qual o membroM
é declarado, são aplicadas as seguintes regras:- Se
M
for uma constante, campo, propriedade, evento ou membro de enumeração, todos os membros declarados em um tipo baseS
serão removidos do conjunto. - Se
M
for uma declaração de tipo, todos os tipos não declarados em um tipo baseS
serão removidos do conjunto e todas as declarações de tipo com o mesmo número de parâmetros de tipo queM
declaradas em um tipo base deS
serão removidas do conjunto. - Se
M
for um método, todos os membros não método declarados em um tipo baseS
serão removidos do conjunto.
- Se
- Em seguida, removem-se do conjunto os membros da interface que são ocultados pelos membros da classe. Essa etapa só terá efeito se
T
for um parâmetro de tipo eT
tiver uma classe base eficaz diferente deobject
e um conjunto de interfaces eficazes não vazios (§15.2.5). Para cada membroS.M
no conjunto, em queS
for o tipo no qual o membroM
é declarado, as seguintes regras serão aplicadas seS
for uma declaração de classe diferente deobject
:- Se
M
for uma constante, campo, propriedade, evento, membro de enumeração ou declaração de tipo, todos os membros declarados em uma declaração de interface serão removidos do conjunto. - Se
M
for um método, todos os membros que não sejam método declarados em uma declaração de interface serão removidos do conjunto e todos os métodos com a mesma assinatura queM
declarados em uma declaração de interface serão removidos do conjunto.
- Se
- Finalmente, tendo removido os membros ocultos, o resultado da pesquisa é determinado:
- Se o conjunto consistir em um único membro que não seja um método, esse membro será o resultado da pesquisa.
- Caso contrário, se o conjunto contiver apenas métodos, esse grupo de métodos será o resultado da pesquisa.
- Caso contrário, a pesquisa será ambígua e ocorrerá um erro no momento da associação.
Para pesquisas de membro em tipos que não sejam parâmetros de tipo e interfaces, e pesquisas de membro em interfaces que são estritamente de herança única (cada interface na cadeia de herança tem exatamente zero ou uma interface base direta), o efeito das regras de pesquisa é simplesmente que membros derivados ocultam os membros base que possuem o mesmo nome ou a mesma assinatura. Essas pesquisas de herança única nunca são ambíguas. As ambiguidades que podem surgir de consultas de membros em interfaces de herança múltipla são descritas em §18.4.6.
Observação: essa fase representa apenas um tipo de ambiguidade. Se a pesquisa de membro resultar em um grupo de métodos, os usos adicionais do grupo de métodos poderão falhar devido à ambiguidade, por exemplo, conforme descrito em §12.6.4.1 e §12.6.6.2. fim da observação
12.5.2 Tipos de base
Para fins de consulta de membro, um tipo T
é considerado como tendo os seguintes tipos base:
- Se
T
forobject
oudynamic
, entãoT
não terá nenhum tipo base. - Se
T
for enum_type, os tipos de base deT
serão os tipos de classeSystem.Enum
,System.ValueType
eobject
. - Se
T
for struct_type, os tipos de baseT
serão os tipos de classeSystem.ValueType
eobject
.Observação: nullable_value_type é struct_type (§8.3.1). fim da observação
- Se
T
for class_type, os tipos de baseT
serão as classes de base deT
, incluindo o tipo de classeobject
. - Se
T
for interface_type, os tipos de baseT
serão as interfaces de baseT
e o tipo de classeobject
. - Se
T
for array_type, os tipos de baseT
são tipos de classeSystem.Array
eobject
. - Se
T
for delegate_type, os tipos de baseT
serão os tipos de classeSystem.Delegate
eobject
.
12.6 Membros de função
12.6.1 Geral
Membros de função são membros que contêm instruções executáveis. Os membros de função são sempre membros de tipos e não podem ser membros de namespaces. O C# define as seguintes categorias de membros de função:
- Métodos
- Propriedades
- Eventos
- Indexadores
- Operadores definidos pelo usuário
- Construtores de instância
- Construtores estáticos
- Finalizadores
Exceto para finalizadores e construtores estáticos (que não podem ser invocados explicitamente), as instruções contidas nos membros da função são executadas por invocações de membro de função. A sintaxe real para escrever uma invocação de membro de função depende da categoria de membro de função específica.
A lista de argumentos (§12.6.2) de uma invocação de membro de função fornece valores reais ou referências variáveis para os parâmetros do membro de função.
Invocações de métodos genéricos podem empregar inferência de tipo para determinar o conjunto de argumentos de tipo a serem passados para o método. Esse processo é descrito em §12.6.3.
Invocações de métodos, indexadores, operadores e construtores de instância empregam o mecanismo de resolução de sobrecarga para determinar qual membro de função de um conjunto de candidatos deve ser invocado. Esse processo é descrito em §12.6.4.
Depois que um membro de função específico tiver sido identificado no momento da associação, possivelmente por meio da resolução de sobrecarga, o processo real de run-time para invocar o membro de função é descrito em §12.6.6.
Observação: a tabela a seguir resume o processamento que ocorre em constructos que envolvem as seis categorias de membros de função que podem ser invocadas explicitamente. Na tabela
e
x
,y
evalue
indicam expressões classificadas como variáveis ou valores;T
indica uma expressão classificada como um tipo;F
é o nome simples de um método eP
é o nome simples de uma propriedade.
Constructo Exemplo Descrição Invocação de método F(x, y)
A resolução de sobrecarga é aplicada para selecionar o melhor método F
na classe ou struct que o contém. O método é invocado com a lista de argumentos(x, y)
. Se o método não forstatic
, a expressão de instância seráthis
.T.F(x, y)
A resolução de sobrecarga é aplicada para selecionar o melhor método F
na classe ou structT
. Ocorrerá um erro de tempo de associação se o método não forstatic
. O método é invocado com a lista de argumentos(x, y)
.e.F(x, y)
A resolução de sobrecarga é aplicada para selecionar o melhor método F
na classe, estrutura ou interface fornecida pelo tipo dee
. Ocorrerá um erro de tempo de associação se o método forstatic
. O método é invocado com a expressão de instânciae
e a lista de argumentos(x, y)
.Acesso à propriedade P
O acessador get da propriedade P
na classe ou estrutura contida é invocado. Ocorrerá um erro de tempo de compilação seP
for somente gravação. SeP
não forstatic
, a expressão de instância seráthis
.P = value
O acessador set da propriedade P
na classe ou estrutura contida é invocado com a lista de argumentos(value)
. Ocorrerá um erro de tempo de compilação seP
for somente leitura. SeP
não forstatic
, a expressão de instância seráthis
.T.P
O acessador get da propriedade P
na classe ou estruturaT
é invocado. Ocorrerá um erro de tempo de compilação seP
não forstatic
ou seP
for somente leitura.T.P = value
O acessador set da propriedade P
na classe ou estruturaT
é invocado com a lista de argumentos(value)
. Ocorrerá um erro de compilação seP
não forstatic
ou seP
for somente leitura.e.P
O acessador get da propriedade P
na classe, estrutura ou interface fornecida pelo tipo deE
é invocado com a expressão de instânciae
. Ocorrerá um erro de tempo de associação seP
forstatic
ou seP
for somente gravação.e.P = value
O acessador set da propriedade P
na classe, estrutura ou interface fornecida pelo tipo deE
é invocado com a expressão de instânciae
e a lista de argumentos(value)
. Ocorrerá um erro de tempo de associação seP
forstatic
ou seP
for somente leitura.Acesso de evento E += value
O acessador add do evento E
na classe ou estrutura contida é invocado. SeE
não forstatic
, a expressão de instância seráthis
.E -= value
O acessador remove do evento E
na classe ou estrutura contida é invocado. SeE
não forstatic
, a expressão de instância seráthis
.T.E += value
O acessador add do evento E
na classe ou estruturaT
é invocado. Ocorrerá um erro de tempo de associação seE
não forstatic
.T.E -= value
O acessador remove do evento E
na classe ou estruturaT
é invocado. Ocorrerá um erro de tempo de associação seE
não forstatic
.e.E += value
O acessador de adição do evento E
na classe, struct ou interface fornecida pelo tipo deE
é invocado com a expressão de instânciae
. Ocorrerá um erro de tempo de associação seE
forstatic
.e.E -= value
O acessador remove do evento E
na classe, estrutura ou interface fornecida pelo tipo deE
é invocado com a expressão de instânciae
. Ocorrerá um erro de tempo de associação seE
forstatic
.Acesso de indexador e[x, y]
A resolução de sobrecarga é aplicada para selecionar o melhor indexador na classe, estrutura ou interface fornecida pelo tipo de e
. O acessador 'get' do indexador é invocado com a expressão de instânciae
e a lista de argumentos(x, y)
. Ocorrerá um erro de tempo de associação se o indexador for somente gravação.e[x, y] = value
A resolução de sobrecarga é aplicada para selecionar o melhor indexador na classe, estrutura ou interface fornecida pelo tipo de e
. O acessador set do indexador é invocado com a expressão de instânciae
e a lista de argumentos(x, y, value)
. Ocorrerá um erro de tempo de associação se o indexador for somente leitura.Invocação do operador -x
A resolução de sobrecarga é aplicada para selecionar o melhor operador unário na classe ou estrutura fornecida pelo tipo de x
. O operador selecionado é invocado com a lista de argumentos(x)
.x + y
A resolução de sobrecarga é aplicada para selecionar o melhor operador binário nas classes ou structs fornecidos pelos tipos de x
ey
. O operador selecionado é invocado com a lista de argumentos(x, y)
.Invocação do construtor de instância new T(x, y)
A resolução de sobrecarga é aplicada para selecionar o melhor construtor de instância na classe ou estrutura T
. O construtor de instância é invocado com a lista de argumentos(x, y)
.fim da observação
12.6.2 Listas de argumentos
12.6.2.1 Geral
Cada invocação de membro de função e delegado inclui uma lista de argumentos, que fornece valores reais ou referências de variável para os parâmetros do membro da função. A sintaxe para especificar a lista de argumentos de uma invocação de membro de função depende da categoria de membro da função:
- Por exemplo para construtores, métodos, indexadores e delegados, os argumentos são especificados como argument_list conforme descrito abaixo. Para indexadores, ao invocar o acessador set, a lista de argumentos inclui também a expressão especificada como o operando direito do operador de atribuição.
Observação: esse argumento adicional não é utilizado para resolução de sobrecarga, apenas durante a invocação do acessador set. fim da observação
- Para propriedades, a lista de argumentos está vazia ao invocar o acessador get, e consiste na expressão especificada como o operando direito do operador de atribuição ao invocar o acessador set.
- Para eventos, a lista de argumentos consiste na expressão especificada como o operando direito do operador
+=
ou-=
. - Para operadores definidos pelo usuário, a lista de argumentos consiste no operando único do operador unário ou nos dois operandos do operador binário.
Os argumentos de propriedades (§15.7) e eventos (§15.8) são sempre passados como parâmetros de valor (§15.6.2.2). Os argumentos de operadores definidos pelo usuário (§15.10) são sempre passados como parâmetros de valor (§15.6.2.2) ou parâmetros de entrada (§9.2.8). Os argumentos dos indexadores (§15.9) são sempre passados como parâmetros de valor (§15.6.2.2), parâmetros de entrada (§9.2.8) ou matrizes de parâmetro (§15.6.2.4). Não há suporte para parâmetros de saída e referência para essas categorias de membros de função.
Os argumentos de um construtor de instância, método, indexador ou invocação de delegado são especificados como argument_list:
argument_list
: argument (',' argument)*
;
argument
: argument_name? argument_value
;
argument_name
: identifier ':'
;
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' variable_reference
;
argument_list consiste em um ou mais argumentos separados por vírgulas. Cada argumento consiste em um argument_name opcional seguido por um argument_value. Um argumento com argument_name é chamado de argumento denominado, enquanto um argumento sem argument_name é um argumento posicional.
O argument_value pode assumir uma das seguintes formas:
- Uma expressão, indicando que o argumento é passado como um parâmetro de valor ou é transformado em um parâmetro de entrada e, em seguida, passado como esse, conforme determinado por (§12.6.4.2 e descrito em §12.6.2.3.
- A palavra-chave
in
seguida por variable_reference (§9.5), indicando que o argumento é passado como um parâmetro de entrada (§15.6.2.3.2). Uma variável deve ser definitivamente atribuída (§9.4) antes que possa ser passada como um parâmetro de entrada. - A palavra-chave
ref
seguida por variable_reference (§9.5), indicando que o argumento é passado como um parâmetro de referência (§15.6.2.3.3). Uma variável deve ser definitivamente atribuída (§9.4) antes que possa ser passada como um parâmetro de referência. - A palavra-chave
out
seguida por variable_reference (§9.5), indicando que o argumento é passado como um parâmetro de saída (§15.6.2.3.4). Uma variável é considerada definitivamente atribuída (§9.4) após uma invocação de membro de função na qual a variável é passada como um parâmetro de saída.
O formato determina o modo parameter-passing do argumento: valor, entrada, referência ou saída, respectivamente. No entanto, conforme mencionado acima, um argumento com o modo de passagem por valor pode ser transformado em um com o modo de passagem por entrada.
Passar um campo volátil (§15.5.4) como um parâmetro de entrada, saída ou referência causa um aviso, pois o campo não pode ser tratado como volátil pelo método invocado.
12.6.2.2 Parâmetros correspondentes
Para cada argumento em uma lista de argumentos, deve haver um parâmetro correspondente no membro de função ou delegado que está sendo invocado.
A lista de parâmetros usada abaixo é determinada da seguinte maneira:
- Para métodos virtuais e indexadores definidos em classes, a lista de parâmetros é escolhida na primeira declaração ou substituição do membro da função encontrado ao começar com o tipo estático do receptor e pesquisar nas classes base.
- Para métodos parciais, a lista de parâmetros da declaração de método parcial definidora é usada.
- Para todos os outros delegados e membros de função, há apenas uma lista de parâmetros, que é aquela usada.
A posição de um argumento ou parâmetro é definida como o número de argumentos ou parâmetros que o precedem na lista de argumentos ou na lista de parâmetros.
Os parâmetros correspondentes para argumentos de membro de função são estabelecidos da seguinte maneira:
- Argumentos na argument_list de construtores de instância, métodos, indexadores e delegados:
- Um argumento posicional em que um parâmetro ocorre na mesma posição na lista de parâmetros corresponde a esse parâmetro, a menos que o parâmetro seja uma matriz de parâmetros e o membro de função seja invocado em sua forma expandida.
- Um argumento posicional de um membro de função com uma matriz de parâmetros invocada em sua forma expandida, que ocorre na ou após a posição da matriz de parâmetros na lista de parâmetros, corresponde a um elemento na matriz de parâmetros.
- Um argumento denominado corresponde ao parâmetro do mesmo nome na lista de parâmetros.
- Para indexadores, ao invocar o acessador set, a expressão especificada como o operando direito do operador de atribuição corresponde ao parâmetro implícito
value
da declaração do acessador set.
- Para propriedades, ao invocar o acessador get, não há argumentos. Ao invocar o acessador set, a expressão especificada como o operando direito do operador de atribuição corresponde ao parâmetro implícito de valor da declaração do acessador set.
- Para operadores unários definidos pelo usuário (incluindo conversões), o operando único corresponde ao único parâmetro da declaração do operador.
- Para operadores binários definidos pelo usuário, o operando esquerdo corresponde ao primeiro parâmetro e o operando à direita corresponde ao segundo parâmetro da declaração do operador.
- Um argumento sem nome corresponde a nenhum parâmetro quando ele está após um argumento nomeado fora de posição ou um argumento nomeado que corresponde a uma matriz de parâmetros.
Observação: isso impede que
void M(bool a = true, bool b = true, bool c = true);
sejam invocados porM(c: false, valueB);
. O primeiro argumento é usado fora de posição (o argumento é usado na primeira posição, mas o parâmetro denominadoc
está na terceira posição); portanto, os argumentos a seguir devem ser denominados. Em outras palavras, os argumentos nomeados que não são os últimos só são permitidos quando o nome e a posição resultam na identificação do mesmo parâmetro correspondente. fim da observação
12.6.2.3 Avaliação em run-time de listas de argumentos
Durante o processamento em run-time de uma invocação de membro de função (§12.6.6), as expressões ou referências de variáveis de uma lista de argumentos são avaliadas em ordem, da esquerda para a direita, da seguinte maneira:
Para um argumento de valor, se o modo de passagem do parâmetro for valor
a expressão de argumento é avaliada e uma conversão implícita (§10.2) para o tipo de parâmetro correspondente é executada. O valor resultante torna-se o valor inicial do parâmetro de valor na invocação do membro da função.
Caso contrário, o modo de passagem do parâmetro é de entrada. Se o argumento for uma referência variável e existir uma conversão de identidade (§10.2.2) entre o tipo do argumento e o tipo do parâmetro, o local de armazenamento resultante se tornará o local de armazenamento representado pelo parâmetro na invocação do membro da função. Caso contrário, um local de armazenamento será criado com o mesmo tipo do parâmetro correspondente. A expressão de argumento é avaliada e uma conversão implícita (§10.2) para o tipo de parâmetro correspondente é executada. O valor resultante é armazenado dentro desse local de armazenamento. Esse local de armazenamento é representado pelo parâmetro de entrada na invocação do membro da função.
Exemplo: dadas as seguintes declarações e chamadas de método:
static void M1(in int p1) { ... } int i = 10; M1(i); // i is passed as an input argument M1(i + 5); // transformed to a temporary input argument
Na
M1(i)
chamada de método,i
é passado como um argumento de entrada, pois é classificado como uma variável e tem o mesmo tipoint
que o parâmetro de entrada. Na chamada do métodoM1(i + 5)
, uma variável sem nomeint
é criada, inicializada com o valor passado como argumento e então passada como um argumento de entrada. Consulte §12.6.4.2 e §12.6.4.4.fim do exemplo
Para um argumento de entrada, saída ou referência, a referência de variável é avaliada e o local de armazenamento resultante torna-se o local de armazenamento representado pelo parâmetro na invocação do membro da função. Para um argumento de entrada ou referência, a variável deve ser atribuída definitivamente no ponto da chamada de método. Se a referência de variável for fornecida como um argumento de saída ou for um elemento de matriz de um reference_type, uma verificação em run-time será executada para garantir que o tipo de elemento da matriz seja idêntico ao tipo do parâmetro. Se essa verificação falhar,
System.ArrayTypeMismatchException
será gerado.
Observação: essa verificação em run-time é necessária devido à covariância da matriz (§17.6). fim da observação
Exemplo: no código a seguir
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }
A segunda invocação de
F
faz com que umSystem.ArrayTypeMismatchException
seja lançado porque o tipo de elemento real deb
éstring
e nãoobject
.fim do exemplo
Métodos, indexadores e construtores de instância podem declarar seu parâmetro mais correto como uma matriz de parâmetros (§15.6.2.4). Esses membros de função são invocados em sua forma normal ou em sua forma expandida, dependendo do que é aplicável (§12.6.4.2):
- Quando um membro de função com uma matriz de parâmetros é invocado em sua forma normal, o argumento fornecido para a matriz de parâmetros deve ser uma única expressão que é conversível implicitamente (§10.2) para o tipo de matriz de parâmetros. Nesse caso, a matriz de parâmetros age precisamente como um parâmetro de valor.
- Quando membro de função com uma matriz de parâmetros é invocado em sua forma expandida, a invocação deve especificar zero ou mais argumentos posicionais para a matriz de parâmetros, em que cada argumento é uma expressão conversível implicitamente (§10.2) para o tipo de elemento da matriz de parâmetros. Nesse caso, a invocação cria uma instância do tipo de matriz de parâmetros com um tamanho correspondente ao número de argumentos, inicializa os elementos da instância da matriz com os valores de argumento fornecidos e usa a instância de matriz recém-criada como o argumento real.
As expressões de uma lista de argumentos são sempre avaliadas em ordem textual.
Exemplo: Assim, o exemplo
class Test { static void F(int x, int y = -1, int z = -2) => Console.WriteLine($"x = {x}, y = {y}, z = {z}"); static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
produz a saída
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
fim do exemplo
Quando um membro de função com uma matriz de parâmetros é invocado em sua forma expandida com pelo menos um argumento expandido, a invocação é processada como se uma expressão de criação de matriz com um inicializador de matriz (§12.8.17.4) fosse inserida em torno dos argumentos expandidos. Uma matriz vazia é passada quando não há argumentos para a matriz de parâmetros; não é especificado se a referência passada é para uma matriz vazia recém-alocada ou existente.
Exemplo: dada a declaração
void F(int x, int y, params object[] args);
as invocações a seguir do formato expandido do método
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);
correspondem exatamente a
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });
fim do exemplo
Quando os argumentos são omitidos de um membro de função com parâmetros opcionais correspondentes, os argumentos padrão da declaração do membro da função são passados implicitamente. (Isso pode envolver a criação de um local de armazenamento, conforme descrito acima.)
Observação: como eles são sempre constantes, sua avaliação não afetará a avaliação dos argumentos restantes. fim da observação
12.6.3 Inferência de tipos
12.6.3.1 Geral
Quando um método genérico é chamado sem especificar argumentos de tipo, um processo de inferência de tipo tenta inferir os argumentos de tipo para a chamada. A presença de inferência de tipo permite que uma sintaxe mais conveniente seja usada para chamar um método genérico e permite que o programador evite especificar informações de tipo redundantes.
Exemplo:
class Chooser { static Random rand = new Random(); public static T Choose<T>(T first, T second) => rand.Next(2) == 0 ? first : second; } class A { static void M() { int i = Chooser.Choose(5, 213); // Calls Choose<int> string s = Chooser.Choose("apple", "banana"); // Calls Choose<string> } }
Por meio da inferência de tipo, os argumentos de tipo
int
estring
são determinados direto dos argumentos para o método.fim do exemplo
A inferência de tipos ocorre como parte do processamento em tempo de associação de uma invocação de método (§12.8.10.2) e ocorre antes da etapa de resolução de sobrecarga da invocação. Quando um grupo de métodos específico é definido em uma invocação de método e nenhum argumento de tipo é definido como parte da invocação do método, a inferência de tipo é aplicada a cada método genérico no grupo de métodos. Se a inferência de tipo for bem-sucedida, os argumentos de tipo inferido serão usados para determinar os tipos de argumentos para resolução de sobrecarga subsequente. Se a resolução de sobrecarga escolher um método genérico como aquele a ser invocado, os argumentos de tipo inferidos serão usados como argumentos de tipo para a invocação. Se a inferência de tipo para um método específico falhar, esse método não participará da resolução de sobrecarga. A falha de inferência de tipos, por si só, não causa um erro de tempo de associação. No entanto, isso geralmente leva a um erro de tempo de vinculação quando a resolução de sobrecarga falha em encontrar qualquer método aplicável.
Se cada argumento fornecido não corresponder a exatamente um parâmetro no método (§12.6.2.2), ou houver um parâmetro não opcional sem nenhum argumento correspondente, a inferência falhará imediatamente. Caso contrário, suponha que o método genérico tenha a seguinte assinatura:
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Com uma chamada de método do formato M(E₁ ...Eₓ)
a tarefa de inferência de tipo é localizar argumentos de tipo exclusivos S₁...Sᵥ
para cada um dos parâmetros de tipo X₁...Xᵥ
para que a chamada M<S₁...Sᵥ>(E₁...Eₓ)
se torne válida.
O processo de inferência de tipo é descrito abaixo como um algoritmo. Um compilador compatível pode ser implementado usando-se uma abordagem alternativa, desde que atinja o mesmo resultado em todos os casos.
Durante o processo de inferência, cada parâmetro de tipo Xᵢ
é fixo em um determinado tipo Sᵢ
ou não fixo com um conjunto associado de limites. Cada um dos limites é algum tipo T
. Inicialmente, cada variável de tipo Xᵢ
não está fixada com um conjunto vazio de limites.
A inferência de tipo ocorre em fases. Cada fase tentará inferir argumentos de tipo para mais variáveis de tipo com base nos resultados da fase anterior. A primeira fase faz algumas inferências iniciais de limites, enquanto a segunda fase corrige variáveis de tipo para tipos específicos e infere limites adicionais. A segunda fase pode ter que ser repetida várias vezes.
Observação: a inferência de tipo também é usada em outros contextos, incluindo a conversão de grupos de métodos (§12.6.3.14) e para encontrar o melhor tipo comum de um conjunto de expressões (§12.6.3.15). fim da observação
12.6.3.2 A primeira fase
Para cada um dos argumentos do método Eᵢ
:
- Se
Eᵢ
for uma função anônima, uma inferência de tipo de parâmetro explícito (§12.6.3.8) será feita deEᵢ
paraTᵢ
- Caso contrário, se
Eᵢ
tiver um tipoU
e o parâmetro correspondente for um parâmetro de valor (§15.6.2.2), uma inferência de limite inferior (§12.6.3.10) será feita deU
paraTᵢ
. - Caso contrário, se
Eᵢ
tiver um tipoU
e o parâmetro correspondente for um parâmetro de referência (§15.6.2.3.3.3) ou parâmetro de saída (§15.6.2 .3.4), uma inferência exata (§12.6.3.9) será feita deU
paraTᵢ
. - Caso contrário, se
Eᵢ
tiver um tipoU
e o parâmetro correspondente for um parâmetro de entrada (§15.6.2.3.2) eEᵢ
for um argumento de entrada, então uma inferência exata (§12.6.3.9) será feita deU
paraTᵢ
. - Caso contrário, se
Eᵢ
tiver umU
de tipo e o parâmetro correspondente for um parâmetro de entrada (§15.6.2.3.2), então uma inferência de limite inferior (§12.6.3.10) será feita deU
paraTᵢ
. - Caso contrário, nenhuma inferência será feita para esse argumento.
12.6.3.3 A segunda fase
A segunda fase continua da seguinte maneira:
- Todas as variáveis de tipo não fixas
Xᵢ
que não dependem de (§12.6.3.6) nenhumXₑ
são fixas (§12.6.3.12). - Se tais variáveis de tipo não existirem, todas as variáveis de tipo não fixas
Xᵢ
serão fixadas para as quais todas as seguintes condições são válidas:- Há pelo menos uma variável de tipo
Xₑ
que depende deXᵢ
-
Xᵢ
tem um conjunto não vazio de limites
- Há pelo menos uma variável de tipo
- Se não existirem tais variáveis de tipo e ainda houver variáveis de tipo não fixadas, a inferência de tipo falhará.
- Caso contrário, se não houver mais variáveis de tipo não fixas, a inferência do tipos será bem-sucedida.
- Caso contrário, para todos os argumentos
Eᵢ
com o tipo de parâmetro correspondenteTᵢ
em que os tipos de saída (§12.6.3.5) contêm variáveis de tipo não fixasXₑ
, mas os tipos de entrada (§12.6.3.4) não, uma inferência de tipo de saída (§12.6.3.7) é feita deEᵢ
paraTᵢ
. Em seguida, a segunda fase é repetida.
12.6.3.4 Tipos de entrada
Se E
for um grupo de métodos ou uma função anônima com tipagem implícita e T
for um tipo delegado ou tipo de árvore de expressão, todos os tipos de parâmetro de T
serão tipos de entrada deE
com tipoT
.
12.6.3.5 Tipos de saída
Se E
for um grupo de métodos ou uma função anônima e T
for um tipo delegado ou tipo de árvore de expressão, o tipo de retorno de T
será um tipo de saída deE
com o tipoT
.
12.6.3.6 Dependência
Uma variável de tipo não fixaXᵢ
dependerá diretamente de uma variável de tipo de não fixaXₑ
se, para algum argumento Eᵥ
com o tipo Tᵥ
Xₑ
, ocorrer em um tipo de entrada de Eᵥ
com o tipo Tᵥ
e Xᵢ
ocorrer em um tipo de saída de Eᵥ
com o tipo Tᵥ
.
Xₑ
depende deXᵢ
se Xₑ
depender diretamente deXᵢ
ou se Xᵢ
depender diretamente deXᵥ
e Xᵥ
depender deXₑ
. Assim, "depende de" é o fechamento transitivo, mas não reflexivo de "depende diretamente de".
12.6.3.7 Inferências de tipo de saída
Uma inferência de tipo de saída é feita de uma expressão E
para um tipo T da seguinte maneira:
- Se
E
for uma função anônima com tipo de retorno inferidoU
(§12.6.3.13) eT
seja um tipo delegado ou tipo de árvore de expressão com tipo de retornoTₓ
, então uma inferência de limite inferior (§12.6.3.10) é feita deU
paraTₓ
. - Caso contrário, se
E
for um grupo de métodos eT
for um tipo de árvore de expressão ou tipo delegado com tipos de parâmetroT₁...Tᵥ
e tipo de retornoTₓ
, e a resolução de sobrecarga deE
com os tiposT₁...Tᵥ
produzir um único método com o tipo de retornoU
, uma inferência de limite inferior será feita deU
paraTₓ
. - Caso contrário, se
E
for uma expressão do tipoU
, então é feita uma inferência de limite inferiordeU
paraT
. - Caso contrário, nenhuma inferência será feita.
12.6.3.8 Inferências explícitas de tipo de parâmetro
Uma inferência de tipo de parâmetro explícito é feita de uma expressão E
para um tipo T
da seguinte maneira:
- Se
E
for uma função anônima explicitamente tipada com tipos de parâmetroU₁...Uᵥ
eT
for um tipo delegado ou tipo de árvore de expressão com tipos de parâmetroV₁...Vᵥ
, para cadaUᵢ
, uma inferência exata (§12.6.3.9) será feita deUᵢ
para oVᵢ
correspondente.
12.6.3.9 Inferências exatas
Uma inferência exatade um tipo U
para um tipo V
é feita da seguinte maneira:
- Se
V
for não fixadoXᵢ
,U
será adicionado ao conjunto de limites exatos paraXᵢ
. - Caso contrário, os conjuntos
V₁...Vₑ
eU₁...Uₑ
serão determinados verificando-se se algum dos seguintes casos se aplica:-
V
é um tipo de matrizV₁[...]
eU
é um tipo de matrizU₁[...]
da mesma classificação -
V
é o tipoV₁?
eU
é o tipoU₁
-
V
é um tipo de constructoC<V₁...Vₑ>
eU
é um tipo de constructoC<U₁...Uₑ>
Se qualquer um desses casos se aplicar, uma inferência exata será feita de cadaUᵢ
aoVᵢ
correspondente.
-
- Caso contrário, nenhuma inferência será feita.
12.6.3.10 Inferências de limite inferior
Uma inferência de limite inferior de um tipo U
para um tipo V
é feita da seguinte maneira:
- Se
V
for não fixadoXᵢ
,U
será adicionado ao conjunto de limites inferiores paraXᵢ
. - Caso contrário, se
V
for o tipoV₁?
eU
for o tipoU₁?
uma inferência de limite inferior será feita deU₁
paraV₁
. - Caso contrário, os conjuntos
U₁...Uₑ
eV₁...Vₑ
serão determinados verificando-se se algum dos seguintes casos se aplica:-
V
é um tipo de matrizV₁[...]
eU
é um tipo de matrizU₁[...]
da mesma classificação -
V
é um deIEnumerable<V₁>
,ICollection<V₁>
,IReadOnlyList<V₁>>
,IReadOnlyCollection<V₁>
ouIList<V₁>
eU
é um tipo de matriz unidimensionalU₁[]
-
V
é um tipoclass
,struct
,interface
oudelegate
construído, tipoC<V₁...Vₑ>
e há um tipo exclusivoC<U₁...Uₑ>
de modo queU
(ou, seU
for um tipoparameter
, sua classe base efetiva ou qualquer membro do conjunto de interface efetivo) seja idêntico ainherits
de (direta ou indiretamente) ou implementa (direta ou indiretamente)C<U₁...Uₑ>
. - (A restrição de "exclusividade" significa que, no caso de interface
C<T>{} class U: C<X>, C<Y>{}
, nenhuma inferência é feita ao inferir-se deU
paraC<T>
porqueU₁
pode serX
ouY
.)
Se qualquer um desses casos se aplicar, uma inferência será feita de cadaUᵢ
aVᵢ
correspondente da seguinte maneira: - Se
Uᵢ
não for conhecido como um tipo de referência, será feita uma inferência exata . - Caso contrário, se
U
for um tipo de matriz, uma inferência de limite inferior será feita. - Caso contrário, se
V
forC<V₁...Vₑ>
a inferência dependerá do parâmetro de tipoi-th
deC
:- Se for covariante, uma inferência de limite inferior é realizada.
- Se for contravariante, será feita uma inferência de limite superior.
- Se for invariável, uma inferência exata será feita.
-
- Caso contrário, nenhuma inferência será feita.
12.6.3.11 Inferências de limite superior
Uma inferência de limite superior de um tipo U
para um tipo V
é feita da seguinte maneira:
- Se
V
for não fixadoXᵢ
, entãoU
será adicionado ao conjunto de limites superiores paraXᵢ
. - Caso contrário, os conjuntos
V₁...Vₑ
eU₁...Uₑ
serão determinados verificando-se se algum dos seguintes casos se aplica:-
U
é um tipo de matrizU₁[...]
eV
é um tipo de matrizV₁[...]
da mesma classificação -
U
é um deIEnumerable<Uₑ>
,ICollection<Uₑ>
,IReadOnlyList<Uₑ>
,IReadOnlyCollection<Uₑ>
ouIList<Uₑ>
eV
é um tipo de matriz unidimensionalVₑ[]
-
U
é o tipoU1?
eV
é o tipoV1?
-
U
é um tipo de classe, estrutura, interface ou delegado construídoC<U₁...Uₑ>
eV
é um tipoclass, struct, interface
oudelegate
que éidentical
para,inherits
de (direta ou indiretamente), ou implementa (direta ou indiretamente) um tipo exclusivoC<V₁...Vₑ>
- (A restrição de "exclusividade" significa que, dada uma interface
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
, nenhuma inferência é feita quando se infere deC<U₁>
paraV<Q>
. Não se fazem inferências deU₁
paraX<Q>
ouY<Q>
.)
Se qualquer um desses casos se aplicar, uma inferência será feita de cadaUᵢ
aVᵢ
correspondente da seguinte maneira: - Se
Uᵢ
não for conhecido como um tipo de referência, será feita uma inferência exata . - Caso contrário, se
V
for um tipo de matriz, será feita uma inferência de limite superior - Caso contrário, se
U
forC<U₁...Uₑ>
a inferência dependerá do parâmetro de tipoi-th
deC
:- Se for covariante, será feita uma inferência de limite superior .
- Se for contravariante, então será feita uma inferência de limite inferior.
- Se for invariável, uma inferência exata será feita.
-
- Caso contrário, nenhuma inferência será feita.
12.6.3.12 Correção
Uma variável de tipo não fixaXᵢ
com um conjunto de limites é fixada da seguinte maneira:
- O conjunto de tipos candidatos
Uₑ
começa sendo idêntico ao conjunto de todos os tipos presentes no conjunto de limites paraXᵢ
. - Cada limite de
Xᵢ
é examinado por sua vez: para cada limite exato U deXᵢ
, todos os tiposUₑ
que não são idênticos aU
são removidos do conjunto de candidatos. Para cada limite inferiorU
deXᵢ
, todos os tiposUₑ
para os quais não existe uma conversão implícita deU
são removidos do conjunto de candidatos. Para cada limite superior U deXᵢ
, todos os tiposUₑ
dos quais não existe uma conversão implícita paraU
são removidos do conjunto de candidatos. - Se entre os tipos de candidatos restantes
Uₑ
houver um tipo exclusivoV
para o qual há uma conversão implícita de todos os outros tipos de candidatos,Xᵢ
será corrigido paraV
. - Caso contrário, a inferência de tipo falhará.
12.6.3.13 Tipo de retorno inferido
O tipo de retorno inferido de uma função anônima F
é usado durante a inferência de tipos e a resolução de sobrecarga. O tipo de retorno inferido só pode ser determinado para uma função anônima em que todos os tipos de parâmetro são conhecidos, seja porque são dados explicitamente, prestados por meio de uma conversão de função anônima ou inferidos durante a inferência de tipo em uma invocação de método genérico envolvente.
O tipo de retorno efetivo inferido é determinado da seguinte maneira:
- Se o corpo de
F
for uma expressão que tenha um tipo, o tipo de retorno efetivo inferido deF
será o tipo dessa expressão. - Se o corpo de
F
for um bloco e o conjunto de expressões nas instruções do blocoreturn
possuir um tipo comum idealT
(§12.6.3.15), o tipo de retorno efetivo inferido deF
seráT
. - Caso contrário, um tipo de retorno efetivo não poderá ser inferido para
F
.
O tipo de retorno inferido é determinado da seguinte maneira:
- Se
F
for assíncrono e caso o corpo deF
seja uma expressão classificada como nada (§12.2) ou um bloco em que nenhuma instruçãoreturn
contenha expressões, o tipo de retorno inferido é«TaskType»
(§15.14.1). - Se
F
for assíncrono e tiver um tipoT
de retorno efetivo inferido, o tipo de retorno inferido será«TaskType»<T>»
(§15.14.1). - Se
F
for não assíncrono e tiver um tipo de retorno efetivo inferidoT
, o tipo de retorno inferido seráT
. - Caso contrário, um tipo de retorno não poderá ser inferido para
F
.
Exemplo: como exemplo de inferência de tipo envolvendo funções anônimas, considere o método de extensão
Select
declarado na classeSystem.Linq.Enumerable
:namespace System.Linq { public static class Enumerable { public static IEnumerable<TResult> Select<TSource,TResult>( this IEnumerable<TSource> source, Func<TSource,TResult> selector) { foreach (TSource element in source) { yield return selector(element); } } } }
Supondo que o namespace
System.Linq
tenha sido importado com uma diretivausing namespace
, e considerando uma classeCustomer
com uma propriedadeName
do tipostring
, o métodoSelect
pode ser usado para selecionar os nomes de uma lista de clientes.List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);
A invocação do método de extensão (§12.8.10.3) de
Select
é processada reescrevendo-se a invocação para uma invocação de método estático:IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
Como os argumentos de tipo não foram especificados explicitamente, a inferência de tipo é usada para inferir os argumentos de tipo. Primeiro, o argumento dos clientes está relacionado ao parâmetro de origem, inferindo-se
TSource
serCustomer
. Em seguida, usando-se o processo de inferência de tipo de função anônimo descrito acima,c
recebe o tipoCustomer
e a expressãoc.Name
está relacionada ao tipo de retorno do parâmetro seletor, inferindo-seTResult
serstring
. Assim, a invocação é equivalente aSequence.Select<Customer,string>(customers, (Customer c) => c.Name)
e o resultado é do tipo
IEnumerable<string>
.O exemplo a seguir demonstra como a inferência de tipo de função anônima permite que as informações de tipo "fluam" entre argumentos em uma invocação de método genérico. Considerando-se o seguinte método e invocação:
class A { static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) { return f2(f1(value)); } static void M() { double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours); } }
A inferência de tipo para a invocação prossegue da seguinte maneira: primeiro, o argumento "1:15:30" está relacionado ao parâmetro de valor, inferindo
X
serstring
. Em seguida, o parâmetro da primeira função anônima,s
, recebe o tipo inferidostring
e a expressãoTimeSpan.Parse(s)
está relacionada ao tipo de retorno def1
, inferindo-seY
a serSystem.TimeSpan
. Finalmente, o parâmetro da segunda função anônima,t
, recebe o tipo inferidoSystem.TimeSpan
e a expressãot.TotalHours
está relacionada ao tipo de retorno def2
, inferindo-seZ
a serdouble
. Portanto, o resultado da invocação é do tipodouble
.fim do exemplo
12.6.3.14 Inferência de tipo para conversão de grupos de métodos
Semelhante a chamadas de métodos genéricos, a inferência de tipo também deve ser aplicada quando um grupo de métodos M
que contém um método genérico é convertido em um determinado tipo delegado D
(§10.8). Dado um método
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
e o grupo de métodos M
sendo atribuído ao tipo delegado D
a tarefa de inferência de tipo é localizar argumentos de tipo S₁...Sᵥ
para que a expressão:
M<S₁...Sᵥ>
torna-se compatível (§20.2) com D
.
Ao contrário do algoritmo de inferência de tipo para chamadas de métodos genéricos, nesse caso, há apenas tipos de argumentos , nenhuma expressão de argumento . Em particular, não há funções anônimas e, portanto, não há necessidade de várias fases de inferência.
Em vez disso, todos Xᵢ
são considerados não fixos e uma inferência de limite inferior é feita de cada tipo de argumento Uₑ
de D
para o tipo de parâmetro correspondente Tₑ
de M
. Se, para qualquer um dos Xᵢ
, não forem encontrados limites, a inferência de tipos falhará. Caso contrário, todos os Xᵢ
são fixos para Sᵢ
correspondentes, que são o resultado da inferência de tipo.
12.6.3.15 Localizando o melhor tipo comum de um conjunto de expressões
Em alguns casos, um tipo comum precisa ser inferido para um conjunto de expressões. Em particular, os tipos de elementos de matrizes tipadas implicitamente e os tipos de retorno de funções anônimas com corpos de bloco são encontrados dessa forma.
O melhor tipo comum para um conjunto de expressões E₁...Eᵥ
é determinado da seguinte maneira:
- Uma nova variável de tipo não fixa
X
é introduzida. - Para cada expressão
Ei
, é executada uma inferência de tipo de saída (§12.6.3.7) paraX
. -
X
é fixo (§12.6.3.12), se possível, e o tipo resultante é o melhor tipo comum. - Caso contrário, a inferência falhará.
Observação: intuitivamente, essa inferência é equivalente a chamar um método
void M<X>(X x₁ ... X xᵥ)
comEᵢ
como argumentos e inferir-seX
. fim da observação
12.6.4 Resolução de sobrecarga
12.6.4.1 Geral
A resolução de sobrecarga é um mecanismo de tempo de associação para selecionar o melhor membro da função a ser invocado, dado um conjunto de membros de função candidatos e uma lista de argumentos. A resolução de sobrecarga seleciona o membro da função a ser invocado nos seguintes contextos distintos em C#:
- Invocação de um método nomeado em uma invocation_expression (§12.8.10).
- Invocação de um construtor de instância nomeado em uma object_creation_expression (§12.8.17.2).
- Invocação de um acessador de índice por meio de um acesso_de_elemento (§12.8.12).
- Invocação de um operador predefinido ou definido pelo usuário referenciado em uma expressão (§12.4.4 e §12.4.5).
Cada um desses contextos define o conjunto de membros da função candidata e a lista de argumentos à sua maneira exclusiva. Por exemplo, o conjunto de candidatos para uma invocação de método não inclui métodos marcados como substituição (§12.5), e métodos em uma classe base não são candidatos se qualquer método em uma classe derivada for aplicável (§12.8.10.2).
Depois que os membros da função candidata e a lista de argumentos tiverem sido identificados, a seleção do membro da melhor função será a mesma em todos os casos:
- Primeiro, o conjunto de membros da função candidata é reduzido aos membros da função aplicáveis em relação à lista de argumentos determinada (§12.6.4.2). Se esse conjunto reduzido estiver vazio, ocorrerá um erro de tempo de compilação.
- Em seguida, o melhor membro da função do conjunto de membros da função candidata aplicável é posicionado. Se o conjunto contiver apenas um membro de função, esse membro da função será o melhor membro da função. Caso contrário, o melhor membro da função é o membro de uma função que é melhor do que todos os outros membros da função em relação à lista de argumentos fornecida, desde que cada membro da função seja comparado a todos os outros membros da função usando-se as regras em §12.6.4.3. Se não houver exatamente um membro de uma função que seja melhor do que todos os outros membros da função, então a invocação do membro da função será ambígua e ocorrerá um erro de tempo de associação.
As subcláusulas a seguir definem os significados exatos dos termos membro da função aplicável e melhor membro de função.
12.6.4.2 Membro da função aplicável
Um membro da função é considerado um membro da função aplicável em relação a uma lista de argumentos A
quando todos os seguintes itens são verdadeiros:
- Cada argumento em
A
corresponde a um parâmetro na declaração do membro da função, conforme descrito em §12.6.2.2.2, no máximo um argumento corresponde a cada parâmetro e qualquer parâmetro ao qual nenhum argumento corresponde é um parâmetro opcional. - Para cada argumento em
A
, o modo de passagem de parâmetro do argumento é idêntico ao modo de passagem de parâmetro do parâmetro correspondente e- para um parâmetro de valor ou uma matriz de parâmetros, existe uma conversão implícita (§10.2) da expressão de argumento para o tipo do parâmetro correspondente ou
- para um parâmetro de referência ou saída, há uma conversão de identidade entre o tipo da expressão de argumento (se houver) e o tipo do parâmetro correspondente, ou
- para um parâmetro de entrada quando o argumento correspondente tem o modificador
in
, há uma conversão de identidade entre o tipo da expressão de argumento (se houver) e o tipo do parâmetro correspondente, ou - para um parâmetro de entrada quando o argumento correspondente omite o modificador
in
, existe uma conversão implícita (§10.2) da expressão de argumento para o tipo do parâmetro correspondente.
Para uma função membro que inclui uma matriz de parâmetros, se a função membro for aplicável pelas regras acima, ela será aplicável na sua forma normal . Se um membro de função que inclui uma matriz de parâmetros não for aplicável em sua forma normal, o membro da função poderá, em vez disso, ser aplicável em seu formato expandido:
- O formato expandido é construído substituindo-se a matriz de parâmetros na declaração do membro da função por parâmetros de valor zero ou mais do tipo de elemento da matriz de parâmetros, de modo que o número de argumentos na lista de argumentos
A
corresponda ao número total de parâmetros. SeA
tiver menos argumentos do que o número de parâmetros fixos na declaração do membro da função, a forma expandida do membro da função não poderá ser construída e, portanto, não será aplicável. - Caso contrário, o formulário expandido será aplicável se, para cada argumento em
A
, um dos seguintes é verdadeiro:- o modo de passagem de parâmetro do argumento é idêntico ao modo de passagem de parâmetro do parâmetro correspondente e:
- para um parâmetro de valor fixo ou um parâmetro de valor criado pela expansão, existe uma conversão implícita (§10.2) da expressão de argumento para o tipo do parâmetro correspondente; ou
- para um parâmetro por referência, o tipo da expressão de argumento é idêntico ao tipo do parâmetro correspondente.
- o modo de passagem de parâmetro do argumento é valor, e o modo de passagem de parâmetro do parâmetro correspondente é entrada, e uma conversão implícita (§10.2) existe da expressão de argumento para o tipo do parâmetro correspondente.
- o modo de passagem de parâmetro do argumento é idêntico ao modo de passagem de parâmetro do parâmetro correspondente e:
Quando a conversão implícita do tipo de argumento para o tipo de parâmetro de um parâmetro de entrada é uma conversão implícita dinâmica (§10.2.10), os resultados são indefinidos.
Exemplo: dadas as seguintes declarações e chamadas de método:
public static void M1(int p1) { ... } public static void M1(in int p1) { ... } public static void M2(in int p1) { ... } public static void Test() { int i = 10; uint ui = 34U; M1(in i); // M1(in int) is applicable M1(in ui); // no exact type match, so M1(in int) is not applicable M1(i); // M1(int) and M1(in int) are applicable M1(i + 5); // M1(int) and M1(in int) are applicable M1(100u); // no implicit conversion exists, so M1(int) is not applicable M2(in i); // M2(in int) is applicable M2(i); // M2(in int) is applicable M2(i + 5); // M2(in int) is applicable }
fim do exemplo
- Um método estático só será aplicável se o grupo de métodos resultar de um simple_name ou de um member_access por um tipo.
- Um método de instância só será aplicável se o grupo de métodos resultar de um simple_name, um member_access por meio de uma variável ou valor ou um base_access.
- Se o grupo de métodos vier de um simple_name, um método de instância só será aplicável se o acesso
this
for permitido §12.8.14.
- Se o grupo de métodos vier de um simple_name, um método de instância só será aplicável se o acesso
- Quando o grupo de métodos resulta de member_access que pode ser por meio de uma instância ou de um tipo, conforme descrito em §12.8.7.2, os métodos estáticos e de instância são aplicáveis.
- Um método genérico cujos argumentos de tipo (explicitamente especificados ou inferidos) não atendem a suas restrições não é aplicável.
- No contexto de uma conversão de grupo de métodos, haverá uma conversão de identidade (§10.2.2) ou uma conversão de referência implícita (§10.2.8) do tipo de retorno do método para o tipo de retorno de delegado. Caso contrário, o método candidato não será aplicável.
12.6.4.3 Membro de função melhor
Para determinar o membro de função melhor, uma lista de argumentos simplificada A
é construída contendo apenas as próprias expressões de argumento na ordem em que aparecem na lista de argumentos originais e deixando de fora quaisquer argumentos out
ou ref
.
As listas de parâmetros para cada membro da função candidata são construídas da seguinte maneira:
- O formato expandido será usado se o membro da função for aplicável somente no formato expandido.
- Parâmetros opcionais sem argumentos correspondentes são removidos da lista de parâmetros
- Parâmetros de referência e saída são removidos da lista de parâmetros
- Os parâmetros são reordenados para que ocorram na mesma posição que o argumento correspondente na lista de argumentos.
Dada uma lista de argumentos A
com um conjunto de expressões de argumento {E₁, E₂, ..., Eᵥ}
e dois membros de função aplicáveis Mᵥ
e Mₓ
com tipos de parâmetro {P₁, P₂, ..., Pᵥ}
e {Q₁, Q₂, ..., Qᵥ}
, Mᵥ
é definido como um membro de função melhor do que Mₓ
se
- para cada argumento, a conversão implícita de
Eᵥ
paraQᵥ
não é melhor do que a conversão implícita deEᵥ
paraPᵥ
e - para pelo menos um argumento, a conversão de
Eᵥ
paraPᵥ
é melhor do que a conversão deEᵥ
paraQᵥ
.
Caso as sequências de tipo de parâmetro {P₁, P₂, ..., Pᵥ}
e {Q₁, Q₂, ..., Qᵥ}
sejam equivalentes (ou seja, cada Pᵢ
tem uma conversão de identidade para o Qᵢ
correspondente), as seguintes regras de desempate são aplicadas, em ordem, para determinar o membro de função melhor.
- Se
Mᵢ
for um método não genérico eMₑ
for um método genérico,Mᵢ
será melhor queMₑ
. - Caso contrário, se
Mᵢ
for aplicável em sua forma normal eMₑ
tiver uma matriz de parâmetros e for aplicável somente em sua forma expandida,Mᵢ
será melhor queMₑ
. - Caso contrário, se ambos os métodos tiverem matrizes de parâmetros e forem aplicáveis somente em seus formatos expandidos e se a matriz de parâmetros de
Mᵢ
tiver menos elementos do que a matriz de params deMₑ
,Mᵢ
será melhor do queMₑ
. - Caso contrário, se
Mᵥ
tiver tipos de parâmetro mais específicos do queMₓ
,Mᵥ
será melhor queMₓ
.{R1, R2, ..., Rn}
e{S1, S2, ..., Sn}
representam os tipos de parâmetros não instanciados e não expandidos deMᵥ
eMₓ
. Os tipos de parâmetro deMᵥ
são mais específicos do que os deMₓ
se, para cada parâmetro,Rx
não for menos específico do queSx
, e, para pelo menos um parâmetro,Rx
for mais específico do queSx
:- Um parâmetro de tipo é menos específico do que um parâmetro não tipo.
- Recursivamente, um tipo construído é mais específico do que outro tipo construído (com o mesmo número de argumentos de tipo) se pelo menos um argumento de tipo for mais específico e nenhum argumento de tipo for menos específico do que o argumento de tipo correspondente no outro.
- Um tipo de matriz é mais específico do que outro tipo de matriz (com o mesmo número de dimensões) se o tipo de elemento do primeiro for mais específico do que o tipo de elemento do segundo.
- Caso contrário, se um membro for um operador não elevado e o outro for um operador elevado, o não elevado será melhor.
- Se nenhum membro da função tiver sido considerado melhor e todos os parâmetros de
Mᵥ
tiverem um argumento correspondente, enquanto os argumentos padrão precisarão ser substituídos por pelo menos um parâmetro opcional emMₓ
, entãoMᵥ
será melhor do queMₓ
. - Se para pelo menos um parâmetro
Mᵥ
usa a escolha de passagem de parâmetro melhor (§12.6.4.4) do que o parâmetro correspondente emMₓ
e nenhum dos parâmetros emMₓ
usa uma escolha de passagem de parâmetro melhor do queMᵥ
,Mᵥ
é melhor do queMₓ
. - Caso contrário, nenhum membro de função é melhor.
12.6.4.4 Melhor modo de passagem de parâmetro
É permitido que os parâmetros correspondentes em dois métodos sobrecarregados diferem apenas pelo modo de passagem de parâmetro, desde que um dos dois parâmetros tenha o modo de passagem de valor, da seguinte maneira:
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
Considerando-se int i = 10;
, de acordo com §12.6.4.2, as chamadas M1(i)
e M1(i + 5)
resultam nas duas sobrecargas sendo aplicáveis. Nesses casos, o método com o modo de passagem de parâmetro por valor é a melhor escolha de modo de passagem de parâmetro.
Observação: não existe essa necessidade de opções para argumentos de entrada, saída ou modos de passagem de referência, pois esses argumentos correspondem apenas aos mesmos modos de passagem de parâmetro. fim da observação
12.6.4.5 Melhor conversão da expressão
Considerando uma conversão implícita C₁
que converte de uma expressão E
para um tipo T₁
, e uma conversão implícita C₂
que converte de uma expressão E
para um tipo T₂
, C₁
é uma conversão melhor do que C₂
se uma das seguintes condições for verdadeira:
-
E
corresponde exatamenteT₁
eE
não corresponde exatamenteT₂
(§12.6.4.6) -
E
corresponde exatamente a ambos ou nenhum deT₁
eT₂
, eT₁
é um destino de conversão melhor do queT₂
(§12.6.4.7) -
E
é um grupo de métodos (§12.2),T₁
é compatível (§20.4) com o melhor método do grupo de métodos para conversãoC₁
eT₂
não é compatível com o método mais simples do grupo de métodos para conversãoC₂
12.6.4.6 Expressão exatamente correspondente
Considerando-se uma expressão E
e um tipo T
, E
corresponde exatamente aT
se uma das seguintes condições for:
-
E
tem um tipoS
e existe uma conversão de identidade deS
paraT
-
E
é uma função anônima,T
é um tipo delegateD
ou um tipo de árvore de expressãoExpression<D>
, conforme uma das seguintes condições:- Existe um tipo de retorno inferido,
X
, paraE
no contexto da lista de parâmetros deD
(§12.6.3.12), e existe uma conversão de identidade deX
para o tipo de retorno deD
-
E
é um lambdaasync
sem valor retornado eD
tem um tipo de retorno que é um«TaskType»
não genérico - Ou
E
é não síncrono eD
tem um tipo de retornoY
ouE
é assíncrono eD
tem um tipo de retorno«TaskType»<Y>
(§15.14.1), e uma das seguintes condições se aplica:- O corpo de
E
é uma expressão que corresponde exatamente aY
- O corpo de
E
é um bloco em que cada instrução de retorno retorna uma expressão que corresponde exatamente aY
- O corpo de
- Existe um tipo de retorno inferido,
12.6.4.7 Melhor alvo de conversão
Considerando dois tipos T₁
e T₂
, T₁
é um alvo de conversão melhor do que T₂
se uma das seguintes condições for atendida:
- Existe uma conversão implícita de
T₁
paraT₂
e não existe nenhuma conversão implícita deT₂
paraT₁
-
T₁
é«TaskType»<S₁>
(§15.14.1),T₂
é«TaskType»<S₂>
eS₁
é um destino de conversão melhor do queS₂
-
T₁
é«TaskType»<S₁>
(§15.14.1),T₂
é«TaskType»<S₂>
, eT₁
é mais especializado do queT₂
-
T₁
éS₁
ouS₁?
em queS₁
é um tipo integral assinado eT₂
éS₂
ouS₂?
em queS₂
é um tipo integral sem sinal. Especificamente:-
S₁
ésbyte
eS₂
ébyte
,ushort
,uint
ouulong
-
S₁
éshort
eS₂
éushort
,uint
ouulong
-
S₁
éint
eS₂
éuint
ouulong
. -
S₁
élong
eS₂
éulong
.
-
12.6.4.8 Sobrecarga em classes genéricas
Observação: embora as assinaturas declaradas sejam exclusivas (§8.6), é possível que a substituição de argumentos de tipo resulte em assinaturas idênticas. Em tal situação, a resolução de sobrecarga escolherá a assinatura original mais específica (§12.6.4.3) antes da substituição dos argumentos de tipo, caso exista; caso contrário, relatará um erro. fim da observação
Exemplo: os exemplos a seguir mostram sobrecargas válidas e inválidas de acordo com esta regra:
public interface I1<T> { ... } public interface I2<T> { ... } public abstract class G1<U> { public abstract int F1(U u); // Overload resolution for G<int>.F1 public abstract int F1(int i); // will pick non-generic public abstract void F2(I1<U> a); // Valid overload public abstract void F2(I2<U> a); } abstract class G2<U,V> { public abstract void F3(U u, V v); // Valid, but overload resolution for public abstract void F3(V v, U u); // G2<int,int>.F3 will fail public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail public abstract void F5(U u1, I1<V> v2); // Valid overload public abstract void F5(V v1, U u2); public abstract void F6(ref U u); // Valid overload public abstract void F6(out V v); }
fim do exemplo
12.6.5 Verificação de tempo de compilação da invocação de membro dinâmico
Embora a resolução de sobrecarga de uma operação dinamicamente associada ocorra em run-time, às vezes é possível, em tempo de compilação, conhecer a lista de membros da função da qual uma sobrecarga será escolhida:
- Para uma invocação de delegado (§12.8.10.4), a lista é um membro de função única com a mesma lista de parâmetros que o delegate_type da invocação
- Para uma invocação de método (§12.8.10.2) em um tipo ou em um valor cujo tipo estático não é dinâmico, o conjunto de métodos acessíveis no grupo de métodos é conhecido em tempo de compilação.
- Para uma expressão de criação de objeto (§12.8.17.2) o conjunto de construtores acessíveis no tipo é conhecido em tempo de compilação.
- Para um acesso de indexador (§12.8.12.3), o conjunto de indexadores acessíveis no receptor é conhecido em tempo de compilação.
Nesses casos, é realizada uma verificação limitada em tempo de compilação em cada membro do conjunto conhecido de membros de função, para determinar se é possível afirmar com certeza que nunca serão invocados em tempo de execução. Para cada membro da função F
um parâmetro modificado e uma lista de argumentos são construídos:
- Primeiro, se
F
for um método genérico e argumentos de tipo forem fornecidos, eles serão substituídos pelos parâmetros de tipo na lista de parâmetros. No entanto, se argumentos de tipo não foram fornecidos, nenhuma substituição desse tipo ocorrerá. - Em seguida, qualquer parâmetro cujo tipo está aberto (ou seja, contém um parâmetro de tipo; consulte §8.4.3) é ignorado, juntamente com seus argumentos correspondentes.
Para F
passar na verificação, todas as seguintes condições devem ser atendidas:
- A lista de parâmetros modificada para
F
é aplicável à lista de argumentos modificados em termos de §12.6.4.2. - Todos os tipos construídos na lista de parâmetros modificados atendem às suas restrições (§8.4.5).
- Se os parâmetros de tipo de
F
foram substituídos na etapa acima, suas restrições serão atendidas. - Se
F
for um método estático, o grupo de métodos não deverá ter resultado de um member_access cujo receptor é conhecido em tempo de compilação como uma variável ou valor. - Se
F
for um método de instância, o grupo de métodos não deverá ter resultado de um member_access cujo receptor é conhecido em tempo de compilação como um tipo.
Se nenhum candidato passar neste teste, ocorrerá um erro de tempo de compilação.
12.6.6 Invocação de membro da função
12.6.6.1 Geral
Essa subcláusula descreve o processo que ocorre em run-time para invocar um membro de função específico. Supõe-se que um processo de tempo de associação já determinou o membro específico a ser invocado, possivelmente aplicando a resolução de sobrecarga a um conjunto de membros de funções candidatos.
Para fins de descrever o processo de invocação, os membros da função são divididos em duas categorias:
- Membros de função estática. Esses são métodos estáticos, acessadores de propriedade estáticos e operadores definidos pelo usuário. Membros de função estática são sempre não virtuais.
- Membros de função de instância. Estes são métodos de instância, construtores de instância, acessadores de propriedade de instância e acessadores de indexador. Os membros da função de instância são não virtuais ou virtuais e são sempre invocados em uma instância específica. A instância é computada por uma expressão de instância e torna-se acessível dentro do membro da função como
this
(§12.8.14). Para um construtor de instância, a expressão de instância é considerada o objeto recém-alocado.
O processamento em run-time de uma invocação de membro de função consiste nas seguintes etapas, em que M
é o membro da função e, se M
for um membro da instância, E
é a expressão de instância:
Se
M
for um membro de função estático:- A lista de argumentos é avaliada conforme descrito em §12.6.2.
-
M
é invocado.
Caso contrário, se
E
for um tipo de valorV
eM
for declarado ou substituído emV
:-
E
é avaliado. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. Para um construtor de instância, essa avaliação consiste em alocar armazenamento (normalmente de uma pilha de execução) para o novo objeto. Nesse caso,E
é classificado como uma variável. - Se
E
não for classificado como uma variável ou seV
não for um tipo de struct readonly (§16.2.2), eE
será um dos seguintes:- um parâmetro de entrada (§15.6.2.3.2), ou
- um campo
readonly
(§15.5.3), ou - uma variável de referência
readonly
ou valor de retorno (§9,7)
em seguida, uma variável local temporária do tipo de
E
é criada e o valor deE
é atribuído a essa variável.E
é reclassificada como uma referência a essa variável local temporária. A variável temporária é acessível comothis
dentro deM
, mas não de outra forma. Portanto, somente quandoE
pode ser gravado é possível que o chamador observe as alterações queM
faz parathis
.- A lista de argumentos é avaliada conforme descrito em §12.6.2.
-
M
é invocado. A variável referenciada porE
torna-se a variável referenciada porthis
.
-
Ou:
-
E
é avaliado. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. - A lista de argumentos é avaliada conforme descrito em §12.6.2.
- Se o tipo de
E
for um value_type, uma conversão boxing (§10.2.9) será executada para converterE
em um class_type eE
será considerado como sendo desse class_type nas etapas a seguir. Se o value_type for um enum_type, o class_type seráSystem.Enum;
, caso contrário, seráSystem.ValueType
. - O valor de
E
é verificado como sendo válido. Se o valor deE
for nulo,System.NullReferenceException
será lançado e nenhuma etapa adicional será executada. - A implementação do membro da função a ser invocada é determinada:
- Se o tipo de tempo de associação de
E
for uma interface, o membro da função a ser invocado será a implementação deM
fornecida pelo tipo de run-time da instância referenciada porE
. Esse membro da função é determinado aplicando as regras de mapeamento de interface (§18.6.5) para determinar a implementação deM
fornecida pelo tipo de tempo de execução da instância referenciada porE
. - Caso contrário, se
M
for um membro de função virtual, o membro da função a ser invocado será a implementação deM
fornecida pelo tipo de run-time da instância referenciada porE
. Esse membro da função é determinado aplicando as regras para determinar a implementação mais derivada (§15.6.4) deM
em relação ao tipo de run-time da instância referenciada porE
. - Caso contrário,
M
é um membro de função não virtual e o membro da função a ser invocado éM
si mesmo.
- Se o tipo de tempo de associação de
- A implementação do membro da função determinada na etapa acima é invocada. O objeto referenciado por
E
torna-se o objeto referenciado por isso.
-
O resultado da invocação de um construtor de instância (§12.8.17.2) é o valor criado. O resultado da invocação de qualquer outro membro de função é o valor retornado, se houver, a partir do corpo (§13.10.5).
12.6.6.2 Invocações em instâncias boxed
Um membro da função implementado em um value_type pode ser invocado por meio de uma instância boxed desse value_type nas seguintes situações:
- Quando o membro da função é uma substituição de um método herdado do tipo class_type e é invocado por meio de uma expressão de instância desse class_type.
Observação: class_type sempre será um dos
System.Object
,System.ValueType
ouSystem.Enum
. fim da observação - Quando o membro da função é uma implementação de um membro da função de interface e é invocado por meio de uma expressão de instância de um interface_type.
- Quando o membro da função é invocado por meio de um delegado.
Nessas situações, a instância boxed é considerada como contendo uma variável do value_type, e essa variável se torna a variável referenciada por esta durante a invocação do membro da função.
Observação: em particular, isso significa que, quando um membro da função é invocado em uma instância em caixa, é possível que o membro da função modifique o valor contido na instância em caixa. fim da observação
12.7 Desconstrução
A desconstrução é um processo pelo qual uma expressão é transformada em uma tupla de expressões individuais. A desconstrução é usada quando o destino de uma atribuição simples é uma expressão de tupla, a fim de obter valores a serem atribuídos a cada um dos elementos dessa tupla.
Uma expressão E
é desconstruída em uma expressão de tupla com elementos n
da seguinte maneira:
- Se
E
for uma expressão de tupla comn
elementos, o resultado da desconstrução é a própria expressãoE
. - Caso contrário, se
E
tiver um tipo de tupla(T1, ..., Tn)
com elementosn
,E
será avaliado em uma variável temporária__v
e o resultado da desconstrução será a expressão(__v.Item1, ..., __v.Itemn)
. - Caso contrário, se a expressão
E.Deconstruct(out var __v1, ..., out var __vn)
for resolvida em tempo de compilação para uma instância exclusiva ou método de extensão, essa expressão será avaliada e o resultado da desconstrução será a expressão(__v1, ..., __vn)
. Esse método é chamado de desconstrutor. - Caso contrário,
E
não poderá ser desconstruído.
Aqui, __v
e __v1, ..., __vn
referem-se a variáveis temporárias invisíveis e inacessíveis.
Observação: uma expressão do tipo
dynamic
não pode ser desconstruída. fim da observação
12.8 Expressões primárias
12.8.1 Geral
As expressões primárias incluem as formas mais simples de expressões.
primary_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| tuple_expression
| member_access
| null_conditional_member_access
| invocation_expression
| element_access
| null_conditional_element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| array_creation_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| sizeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
Observação: essa regra gramatical não está pronta para ANTLR, pois faz parte de um conjunto de regras mutuamente recursivas à esquerda (
primary_expression
, ,member_access
,invocation_expression
,element_access
post_increment_expression
, ,post_decrement_expression
enull_forgiving_expression
pointer_member_access
pointer_element_access
) que a ANTLR não manipula. Técnicas padrão podem ser usadas para transformar a gramática para remover a recursão mútua à esquerda. Esse Padrão não faz isso, pois nem todas as estratégias de análise exigem isso (por exemplo, um analisador LALR não faria) e isso ofuscaria a estrutura e a descrição. fim da observação
pointer_member_access (§23.6.3) e pointer_element_access (§23.6.4) só estão disponíveis em código não seguro (§23).
12.8.2 Literais
Uma primary_expression que consiste em um literal (§6.4.5) é classificada como um valor.
12.8.3 Expressões de cadeia de caracteres interpoladas
Uma interpolated_string_expression consiste em $
, $@
ou @$
, imediatamente seguido pelo texto em caracteres "
. No texto entre aspas, há zero ou mais interpolações delimitadas por caracteres {
e }
, cada uma delas inclui uma expressão e especificações opcionais de formatação.
Expressões de cadeia de caracteres interpoladas têm dois formatos; regular (interpolated_regular_string_expression) e verbatim (interpolated_verbatim_string_expression); que são lexicamente semelhantes, mas diferem semanticamente das duas formas de literais de cadeia de caracteres (§6.4.5.6).
interpolated_string_expression
: interpolated_regular_string_expression
| interpolated_verbatim_string_expression
;
// interpolated regular string expressions
interpolated_regular_string_expression
: Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
Interpolated_Regular_String_End
;
regular_interpolation
: expression (',' interpolation_minimum_width)?
Regular_Interpolation_Format?
;
interpolation_minimum_width
: constant_expression
;
Interpolated_Regular_String_Start
: '$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Regular_String_Mid
: Interpolated_Regular_String_Element+
;
Regular_Interpolation_Format
: ':' Interpolated_Regular_String_Element+
;
Interpolated_Regular_String_End
: '"'
;
fragment Interpolated_Regular_String_Element
: Interpolated_Regular_String_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Regular_String_Character
// Any character except " (U+0022), \\ (U+005C),
// { (U+007B), } (U+007D), and New_Line_Character.
: ~["\\{}\u000D\u000A\u0085\u2028\u2029]
;
// interpolated verbatim string expressions
interpolated_verbatim_string_expression
: Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
Interpolated_Verbatim_String_End
;
verbatim_interpolation
: expression (',' interpolation_minimum_width)?
Verbatim_Interpolation_Format?
;
Interpolated_Verbatim_String_Start
: '$@"'
| '@$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Verbatim_String_Mid
: Interpolated_Verbatim_String_Element+
;
Verbatim_Interpolation_Format
: ':' Interpolated_Verbatim_String_Element+
;
Interpolated_Verbatim_String_End
: '"'
;
fragment Interpolated_Verbatim_String_Element
: Interpolated_Verbatim_String_Character
| Quote_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Verbatim_String_Character
: ~["{}] // Any character except " (U+0022), { (U+007B) and } (U+007D)
;
// lexical fragments used by both regular and verbatim interpolated strings
fragment Open_Brace_Escape_Sequence
: '{{'
;
fragment Close_Brace_Escape_Sequence
: '}}'
;
Seis das regras léxicas definidas acima são contextuais da seguinte maneira:
Regra | Requisitos contextuais |
---|---|
Interpolated_Regular_String_Mid | Reconhecido somente após Interpolated_Regular_String_Start, entre quaisquer interpolações subsequentes e antes do Interpolated_Regular_String_Endcorrespondente. |
Regular_Interpolation_Format | Reconhecido somente em regular_interpolation e quando os dois-pontos iniciais (:) não estão aninhados em nenhum tipo de parênteses, chaves ou colchetes. |
Interpolated_Regular_String_End | Reconhecido somente após um Interpolated_Regular_String_Start e apenas se algum token interveniente for Interpolated_Regular_String_Mid ou tokens que possam fazer parte de regular_interpolation, incluindo tokens para qualquer interpolated_regular_string_expression contida nessas interpolações. |
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End | O reconhecimento dessas três regras segue o das regras correspondentes acima com cada mencionado regra gramatical regular substituída pelo texto correspondente. |
Observação: as regras acima são sensíveis ao contexto, pois suas definições se sobrepõem às de outros tokens no idioma. fim da observação
Observação: a gramática acima não está pronta para ANTLR devido às regras de léxicos sensíveis ao contexto. Assim como acontece com outros geradores lexer, o ANTLR dá suporte a regras léxicos sensíveis ao contexto, por exemplo, usando seus modos léxicos , mas esse é um detalhe de implementação e, portanto, não faz parte dessa especificação. fim da observação
interpolated_string_expression é classificado como um valor. Se for convertido imediatamente em System.IFormattable
ou System.FormattableString
com uma conversão de cadeia de caracteres interpolada implícita (§10.2.5), a expressão de cadeia de caracteres interpolada terá esse tipo. Caso contrário, tem o tipo string
.
Observação: as diferenças entre os tipos possíveis de uma interpolated_string_expression podem ser determinadas na documentação para
System.String
(§C.2) eSystem.FormattableString
(§C.3). fim da observação
O significado de uma interpolação, regular_interpolation e verbatim_interpolation, é formatar o valor da expressão como um string
de acordo com o formato especificado pelo Regular_Interpolation_Format ou Verbatim_Interpolation_Formatou de acordo com um formato padrão para o tipo de expressão. A string formatada é modificada pelo interpolation_minimum_width, se houver, para produzir o string
final a ser interpolado na interpolated_string_expression.
Observação: como o formato padrão de um tipo é determinado é detalhado na documentação de
System.String
(§C.2) eSystem.FormattableString
(§C.3). Descrições de formatos padrão, que são idênticas para Regular_Interpolation_Format e Verbatim_Interpolation_Format, podem ser encontradas na documentação doSystem.IFormattable
(§C.4) e em outros tipos na biblioteca padrão (§C). fim da observação
Em um interpolation_minimum_width, a constant_expression deve ter uma conversão implícita para int
. Seja a largura do campo o valor absoluto desta expressão_constante, e o alinhamento seja o sinal (positivo ou negativo) do valor desta expressão_constante:
- Se o valor da largura do campo for menor ou igual ao tamanho da cadeia de caracteres formatada, a cadeia de caracteres formatada não será modificada.
- Caso contrário, a cadeia de caracteres formatada é preenchida com caracteres de espaço em branco para que seu comprimento seja igual à largura do campo:
- Se o alinhamento for positivo, a cadeia de caracteres formatada estará alinhada à direita adicionando o preenchimento.
- Caso contrário, ele é alinhado à esquerda adicionando preenchimento.
O significado geral de uma interpolated_string_expression, incluindo a formatação e o preenchimento acima de interpolações, é definido por uma conversão da expressão em uma invocação de método: se o tipo da expressão é System.IFormattable
ou System.FormattableString
esse método é System.Runtime.CompilerServices.FormattableStringFactory.Create
(§C.3) que retorna um valor do tipo System.FormattableString
; caso contrário, o tipo deve ser string
e o método é string.Format
(§C.2) que retorna um valor do tipo string
.
Em ambos os casos, a lista de argumentos da chamada consiste em um literal de caracteres de formato com especificações de formato para cada interpolação, e um argumento a cada expressão correspondente para as especificações de formato.
O literal da cadeia de caracteres de formato é construído da seguinte maneira, em que N
é o número de interpolações na interpolated_string_expression. O literal de cadeia de caracteres de formato consiste em, em ordem:
- Os caracteres de Interpolated_Regular_String_Start ou Interpolated_Verbatim_String_Start
- Os caracteres de Interpolated_Regular_String_Mid ou Interpolated_Verbatim_String_Mid se houver
- Então se
N ≥ 1
para cada númeroI
de0
aN-1
:- Uma especificação de espaço reservado:
- Um caractere de chave esquerda (
{
) - A representação decimal de
I
- Então, se regular_interpolation ou verbatim_interpolation correspondente tiver interpolation_minimum_width, será usada uma vírgula (
,
) seguida pela representação decimal do valor de constant_expression - Os caracteres de Regular_Interpolation_Format ou Verbatim_Interpolation_Format, se houver, correspondentes a regular_interpolation ou verbatim_interpolation
- Um caractere de chave direita (
}
)
- Um caractere de chave esquerda (
- Os caracteres de Interpolated_Regular_String_Mid ou Interpolated_Verbatim_String_Mid imediatamente após a interpolação correspondente, se houver
- Uma especificação de espaço reservado:
- Os caracteres de Interpolated_Regular_String_End ou Interpolated_Verbatim_String_End.
Os argumentos subsequentes são expressões expressões das interpolações, se houver, em ordem.
Quando interpolated_string_expression contém várias interpolações, as expressões nessas interpolações são avaliadas em ordem textual da esquerda para a direita.
Exemplo:
Este exemplo usa os seguintes recursos de especificação de formato:
- a especificação de formato
X
que formata inteiros como hexadecimal maiúsculo, - o formato padrão de um valor
string
é o próprio valor, - valores de alinhamento positivos que são alinhados à direita dentro da largura de campo mínima especificada,
- valores de alinhamento negativos que são alinhados à esquerda dentro da largura de campo mínima especificada,
- constantes definidas para interpolation_minimum_width e
- que
{{
e}}
são formatados como{
e}
, respectivamente.
Considerando:
string text = "red";
int number = 14;
const int width = -4;
Em seguida:
Expressão de cadeia de caracteres interpolada |
Significado equivalente a string |
Valor |
---|---|---|
$"{text}" |
string.Format("{0}", text) |
"red" |
$"{{text}}" |
string.Format("{{text}}) |
"{text}" |
$"{ text , 4 }" |
string.Format("{0,4}", text) |
" red" |
$"{ text , width }" |
string.Format("{0,-4}", text) |
"red " |
$"{number:X}" |
string.Format("{0:X}", number) |
"E" |
$"{text + '?'} {number % 3}" |
string.Format("{0} {1}", text + '?', number % 3) |
"red? 2" |
$"{text + $"[{number}]"}" |
string.Format("{0}", text + string.Format("[{0}]", number)) |
"red[14]" |
$"{(number==0?"Zero":"Non-zero")}" |
string.Format("{0}", (number==0?"Zero":"Non-zero")) |
"Non-zero" |
fim do exemplo
12.8.4 Nomes simples
simple_name consiste em um identificador, opcionalmente seguido por uma lista de argumentos de tipo:
simple_name
: identifier type_argument_list?
;
Um simple_name é um das formas I
ou I<A₁, ..., Aₑ>
, em que I
é um único identificador e I<A₁, ..., Aₑ>
é um type_argument_list opcional. Quando type_argument_list não é especificado, considere que e
é zero.
simple_name é avaliado e classificado da seguinte maneira:
- Se
e
for zero e simple_name aparecer dentro de um espaço de declaração de variável local (§7.3) que contenha diretamente uma variável local, parâmetro ou constante com o nomeI
, simple_name se referirá a essa variável local, parâmetro ou constante e será classificado como uma variável ou valor. - Se
e
for zero e simple_name aparecer dentro de uma declaração de método genérico, mas fora dos atributos de seu method_declaration, e se essa declaração incluir um parâmetro de tipo com o nomeI
, simple_name se referirá a esse parâmetro de tipo. - Caso contrário, para cada tipo de instância
T
(§15.3.2), começando com o tipo de instância da declaração de tipo imediatamente adjacente e continuando com o tipo de instância de cada declaração de classe ou struct envolvente (se houver):- Se
e
for zero e a declaração deT
incluir um parâmetro de tipo com o nomeI
, simple_name se referirá a esse parâmetro de tipo. - Caso contrário, se uma pesquisa de membro (§12.5) de
I
emT
com argumentos de tipoe
produz uma correspondência:- Se
T
for o tipo de instância da classe ou estrutura que o envolve imediatamente e a pesquisa identificar um ou mais métodos, o resultado será um grupo de métodos com uma expressão de instância associada athis
. Se uma lista de argumentos de tipo tiver sido especificada, ela será usada na chamada de um método genérico (§12.8.10.2). - Caso contrário, se
T
for o tipo de instância da classe ou estrutura imediatamente contida, se a pesquisa identificar um membro de instância e a referência ocorrer dentro do bloco de um construtor de instância, um método de instância ou um acessador de instância (§12.2.1), o resultado será o mesmo que um acesso de membro (§12.8.7) do formatothis.I
. Isso só pode acontecer quandoe
for zero. - Caso contrário, o resultado será o mesmo que um acesso a membro (§12.8.7) da forma
T.I
ouT.I<A₁, ..., Aₑ>
.
- Se
- Se
- Caso contrário, para cada namespace
N
, começando com o namespace no qual o simple_name ocorre, continuando com cada namespace delimitado (se houver) e terminando com o namespace global, as seguintes etapas são avaliadas até que uma entidade esteja localizada:- Se
e
for zero eI
for o nome de um namespace noN
, então:- Se o local em que simple_name ocorre estiver delimitado pela declaração de namespace para
N
e declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeI
com um namespace ou tipo, então simple_name será ambíguo e ocorrerá um erro de tempo de compilação. - Caso contrário, simple_name refere-se ao namespace chamado
I
emN
.
- Se o local em que simple_name ocorre estiver delimitado pela declaração de namespace para
- Caso contrário, se
N
contiver um tipo acessível com o nomeI
e parâmetros de tipoe
, então:- Se
e
for zero e o local onde o simple_name ocorrer estiver delimitado por uma declaração de namespace paraN
e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeI
a um namespace ou tipo, o simple_name será ambíguo e ocorrerá um erro de tempo de compilação. - Caso contrário, namespace_or_type_name refere-se ao tipo construído com os argumentos de tipo fornecidos.
- Se
- Caso contrário, se o local em que o simple_name ocorre estiver dentro de uma declaração de namespace para
N
:- Se
e
for zero e a declaração de namespace contiver extern_alias_directive ou using_alias_directive que associe o nomeI
a um namespace ou tipo importado, simple_name se referirá a esse namespace ou tipo. - Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem exatamente um tipo de nome com parâmetros de tipo
I
ee
, o simple_name se referirá a esse tipo construído com os argumentos de tipo fornecidos. - Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem mais de um tipo com nome
I
e parâmetros de tipoe
, o simple_name será ambíguo e um erro de tempo de compilação ocorrerá.
- Se
Observação: esta etapa inteira é exatamente paralela à etapa correspondente no processamento de namespace_or_type_name (§7.8). fim da observação
- Se
- Caso contrário, se
e
for zero eI
for o identificador_
, o simple_name será um descarte simples, que é um formato de expressão de declaração (§12.17). - Caso contrário, simple_name será indefinido e ocorrerá um erro de tempo de compilação.
12.8.5 Expressões-entre parênteses
Uma expressão com parênteses consiste em uma expressão entre parênteses.
parenthesized_expression
: '(' expression ')'
;
Uma parenthesized_expression é avaliada avaliando a expressão dentro dos parênteses. Se a expressão entre parênteses denotar um namespace ou tipo, ocorrerá um erro de tempo de compilação. Caso contrário, o resultado da parenthesized_expression é o resultado da avaliação da expressão contida.
12.8.6 Expressões de tupla
Uma tuple_expression representa uma tupla e consiste em duas ou mais expressões separadas por vírgulas e, opcionalmente, nomeadas entre parênteses. Uma deconstruction_expression é uma sintaxe abreviada para uma tupla que contém expressões de declaração implicitamente tipadas.
tuple_expression
: '(' tuple_element (',' tuple_element)+ ')'
| deconstruction_expression
;
tuple_element
: (identifier ':')? expression
;
deconstruction_expression
: 'var' deconstruction_tuple
;
deconstruction_tuple
: '(' deconstruction_element (',' deconstruction_element)+ ')'
;
deconstruction_element
: deconstruction_tuple
| identifier
;
Uma tuple_expression é classificada como uma tupla.
Uma deconstruction_expressionvar (e1, ..., en)
é um formato abreviado de tuple_expression(var e1, ..., var en)
e segue o mesmo comportamento. Isso se aplica recursivamente a qualquer deconstruction_tuple aninhada na deconstruction_expression. Cada identificador aninhado em uma deconstruction_expression, portanto, introduz uma expressão de declaração (§12.17). Como resultado, uma deconstruction_expression só pode ocorrer no lado esquerdo de uma atribuição simples.
Uma expressão de tupla terá um tipo se e somente se cada expressão de elemento Ei
tiver um tipo Ti
. O tipo deve ser um tipo de tupla da mesma aridade que a expressão de tupla, em que cada elemento é fornecido pelo seguinte:
- Se o elemento de tupla na posição correspondente tiver um nome
Ni
, o elemento de tipo de tupla deverá serTi Ni
. - Caso contrário, se
Ei
for do formatoNi
,E.Ni
ouE?.Ni
, o elemento de tipo de tupla deverá serTi Ni
, a menos que ocorra qualquer uma das seguintes condições:- Outro elemento da expressão de tupla tem o nome
Ni
, ou - Outro elemento de tupla sem um nome tem uma expressão de elemento de tupla do formato
Ni
,E.Ni
ouE?.Ni
ou -
Ni
é da formaItemX
, em queX
é uma sequência de dígitos decimais que não começam com0
e que podem representar a posição de um elemento de tupla, eX
não representa a posição do elemento.
- Outro elemento da expressão de tupla tem o nome
- Caso contrário, o elemento de tipo de tupla deverá ser
Ti
.
Uma expressão de tupla é avaliada avaliando cada uma das expressões de elemento da esquerda para a direita.
Um valor de tupla pode ser obtido de uma expressão de tupla convertendo-a em um tipo de tupla (§10.2.13), reclassificando-a como um valor (§12.2.2) ou fazendo dela o destino de uma atribuição de desconstrução (§12.21.2).
Exemplo:
(int i, string) t1 = (i: 1, "One"); (long l, string) t2 = (l: 2, null); var t3 = (i: 3, "Three"); // (int i, string) var t4 = (i: 4, null); // Error: no type
Neste exemplo, as quatro expressões de tupla são válidas. As duas primeiras,
t1
et2
, não usam o tipo da expressão de tupla, mas aplicam uma conversão de tupla implícita. No caso det2
, a conversão de tupla implícita depende das conversões implícitas de2
paralong
e denull
parastring
. A terceira expressão de tupla tem um tipo(int i, string)
e, portanto, pode ser reclassificada como um valor desse tipo. A declaração det4
, por outro lado, é um erro: a expressão de tupla não tem tipo porque o segundo elemento não tem tipo.if ((x, y).Equals((1, 2))) { ... };
Este exemplo mostra que, às vezes, as tuplas podem levar a várias camadas de parênteses, especialmente quando a expressão de tupla é o único argumento para uma invocação de método.
fim do exemplo
12.8.7 Acesso de membro
12.8.7.1 Geral
Um member_access consiste em uma primary_expression, um predefined_type ou um qualified_alias_member, seguido por um token ".
", por um identificador e, opcionalmente, por uma type_argument_list.
member_access
: primary_expression '.' identifier type_argument_list?
| predefined_type '.' identifier type_argument_list?
| qualified_alias_member '.' identifier type_argument_list?
;
predefined_type
: 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
| 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
| 'ushort'
;
A produção de qualified_alias_member é definida em §14.8.
member_access é do formato E.I
ou do formato E.I<A₁, ..., Aₑ>
, em que E
é primary_expression, predefined_type ou qualified_alias_member, I
é um único identificador e <A₁, ..., Aₑ>
é type_argument_listopcional. Quando type_argument_list não é especificado, considere que e
é zero.
Um member_access com uma primary_expression do tipo dynamic
é associado dinamicamente (§12.3.3). Nesse caso, o compilador classifica o acesso do membro como um acesso de propriedade do tipo dynamic
. As regras abaixo para determinar o significado do member_access são aplicadas em run-time, usando o tipo de run-time em vez do tipo de tempo de compilação da primary_expression. Se essa classificação em run-time levar a um grupo de métodos, o acesso ao membro será a primary_expression de uma invocation_expression.
member_access é avaliado e classificado da seguinte maneira:
- Se
e
for igual a zero eE
for namespace eE
contiver um namespace aninhado com o nomeI
, o resultado será esse namespace. - Caso contrário, se
E
for um namespace eE
contiver um tipo acessível com parâmetros de tipo de nomeI
eK
, o resultado será esse tipo construído com os argumentos de tipo fornecidos. - Se
E
for classificado como um tipo, seE
não for um parâmetro de tipo e se uma pesquisa de membro (§12.5) deI
emE
com parâmetros de tipoK
produzir uma correspondência, entãoE.I
será avaliada e classificada da seguinte maneira:Observação: quando o resultado dessa pesquisa de membro é um grupo de métodos e
K
é zero, o grupo de métodos pode conter métodos com parâmetros de tipo. Isso permite que esses métodos sejam considerados para inferência de argumento de tipo. fim da observação- Se
I
identificar um tipo, o resultado será esse tipo construído com determinados argumentos de tipo. - Se
I
identificar um ou mais métodos, o resultado será um grupo de métodos sem nenhuma expressão de instância associada. - Se
I
identificar uma propriedade estática, o resultado será um acesso de propriedade sem nenhuma expressão de instância associada. - Se
I
identifica um campo estático:- Se o campo for somente leitura e a referência ocorrer fora do construtor estático da classe ou estrutura na qual o campo é declarado, o resultado será um valor, ou seja, o valor do campo estático
I
emE
. - Caso contrário, o resultado é uma variável, ou seja, o campo estático
I
emE
.
- Se o campo for somente leitura e a referência ocorrer fora do construtor estático da classe ou estrutura na qual o campo é declarado, o resultado será um valor, ou seja, o valor do campo estático
- Se
I
identifica um evento estático:- Se a referência ocorrer dentro da classe ou struct em que o evento é declarado e o evento foi declarado sem event_accessor_declarations (§15.8.1),
E.I
será processado exatamente como seI
fosse um campo estático. - Caso contrário, o resultado é um acesso de evento com uma expressão de instância não associada.
- Se a referência ocorrer dentro da classe ou struct em que o evento é declarado e o evento foi declarado sem event_accessor_declarations (§15.8.1),
- Se
I
identificar uma constante, o resultado será um valor, ou seja, o valor dessa constante. - Se
I
identificar um membro de enumeração, o resultado será um valor, ou seja, o valor desse membro de enumeração. - Caso contrário,
E.I
é uma referência de membro inválida e ocorre um erro de tempo de compilação.
- Se
- Se
E
for um acesso de propriedade, acesso do indexador, variável ou valor, de tipoT
, e uma pesquisa de membro (§12.5) deI
emT
com argumentos de tipoK
produza uma correspondência,E.I
será avaliado e classificado da seguinte maneira:- Primeiro, se
E
for um acesso de propriedade ou indexador, o valor da propriedade ou do acesso do indexador será obtido (§12.2.2) e E será reclassificado como um valor. - Se
I
identificar um ou mais métodos, o resultado será um grupo de métodos sem nenhuma expressão de instância associada deE
. - Se
I
identificar uma propriedade de instância, o resultado será um acesso de propriedade com uma expressão de instância associada deE
e um tipo associado que é o tipo da propriedade. SeT
for um tipo de classe, o tipo associado será escolhido na primeira declaração ou substituição da propriedade encontrada ao começar comT
e pesquisar por meio de suas classes base. - Se
T
for um class_type e seI
identificar um campo de instância desse class_type:- Se o valor de
E
fornull
, umSystem.NullReferenceException
será lançado. - Caso contrário, se o campo for somente leitura e a referência ocorrer fora de um construtor de instância da classe na qual o campo é declarado, o resultado será um valor, ou seja, o valor do campo
I
no objeto referenciado porE
. - Caso contrário, o resultado é uma variável, ou seja, o campo
I
no objeto referenciado porE
.
- Se o valor de
- Se
T
for um struct_type e seI
identificar um campo de instância desse struct_type:- Se
E
for um valor ou se o campo for somente leitura e a referência ocorrer fora de um construtor de instância do struct no qual o campo é declarado, o resultado será um valor, ou seja, o valor do campoI
na instância de struct fornecida peloE
. - Caso contrário, o resultado é uma variável, ou seja, o campo
I
na instância de struct fornecida porE
.
- Se
- Caso
I
identifique um evento de instância:- Se a referência ocorrer dentro da classe ou estrutura em que o evento é declarado, e o evento foi declarado sem event_accessor_declarations (§15.8.1), e a referência não ocorrer como o lado esquerdo de um operador
a +=
ou-=
,E.I
será processado exatamente como seI
fosse um campo de instância. - Caso contrário, o resultado é um acesso de evento com uma expressão de instância associada de
E
.
- Se a referência ocorrer dentro da classe ou estrutura em que o evento é declarado, e o evento foi declarado sem event_accessor_declarations (§15.8.1), e a referência não ocorrer como o lado esquerdo de um operador
- Primeiro, se
- Caso contrário, será feita uma tentativa de processar
E.I
como uma invocação de método de extensão (§12.8.10.3). Se isso falhar,E.I
será uma referência de membro inválida e ocorrerá um erro de tempo de associação.
12.8.7.2 Nomes simples e nomes de tipo idênticos
Em um acesso de membro na forma E.I
, se E
for um identificador único e se o significado de E
como um simple_name (§12.8.4) for uma constante, campo, propriedade, variável local ou parâmetro com o mesmo tipo que o significado de E
como um type_name (§7.8.1), nessa situação, são permitidos os dois possíveis significados de E
. A pesquisa de membro de E.I
nunca é ambígua, pois I
deve necessariamente ser membro do tipo E
nos dois casos. Em outras palavras, a regra simplesmente permite o acesso aos membros estáticos e aos tipos aninhados de E
, onde, de outra forma, ocorreria um erro de tempo de compilação.
Exemplo:
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() => new Color(...); } class A { public «Color» Color; // Field Color of type Color void F() { Color = «Color».Black; // Refers to Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { «Color» c = «Color».White; // Refers to Color.White static member } }
Somente para fins expositórios, dentro da classe
A
, as ocorrências do identificadorColor
que fazem referência ao tipoColor
são delimitadas por«...»
e aquelas que fazem referência ao campoColor
não são.fim do exemplo
12.8.8 Acesso de membro condicional nulo
Um null_conditional_member_access é uma versão condicional do member_access (§12.8.7) e será um erro de tempo de associação se o tipo de resultado for void
. Para uma expressão condicional nula em que o tipo de resultado pode ser void
consulte (§12.8.11).
Um null_conditional_member_access consiste em uma primary_expression seguida pelos dois tokens "?
" e ".
", depois por um identificador coma um type_argument_list opcional, seguido por zero ou mais dependent_accesses, qualquer um dos quais pode ser precedido por um null_forgiving_operator.
null_conditional_member_access
: primary_expression '?' '.' identifier type_argument_list?
(null_forgiving_operator? dependent_access)*
;
dependent_access
: '.' identifier type_argument_list? // member access
| '[' argument_list ']' // element access
| '(' argument_list? ')' // invocation
;
null_conditional_projection_initializer
: primary_expression '?' '.' identifier type_argument_list?
;
Uma expressão null_conditional_member_accessE
é do formato P?.A
. O significado de E
é determinado da seguinte maneira:
Se o tipo de
P
for um tipo de valor anulável:Que
T
seja o tipo deP.Value.A
.Se
T
for um parâmetro de tipo que não é conhecido por ser um tipo de referência ou um tipo de valor não anulável, ocorrerá um erro de tempo de compilação.Se
T
for um tipo de valor não anulável, o tipo deE
seráT?
e o significado deE
será o mesmo que o significado de:((object)P == null) ? (T?)null : P.Value.A
Exceto que
P
é avaliado apenas uma vez.Caso contrário, o tipo de
E
éT
e o significado deE
é o mesmo que o significado de:((object)P == null) ? (T)null : P.Value.A
Exceto que
P
é avaliado apenas uma vez.
Ou:
Seja
T
o tipo da expressãoP.A
.Se
T
for um parâmetro de tipo que não é conhecido por ser um tipo de referência ou um tipo de valor não anulável, ocorrerá um erro de tempo de compilação.Se
T
for um tipo de valor não anulável, o tipo deE
seráT?
e o significado deE
será o mesmo que o significado de:((object)P == null) ? (T?)null : P.A
Exceto que
P
é avaliado apenas uma vez.Caso contrário, o tipo de
E
éT
e o significado deE
é o mesmo que o significado de:((object)P == null) ? (T)null : P.A
Exceto que
P
é avaliado apenas uma vez.
Observação: em uma expressão do formato:
P?.A₀?.A₁
Se
P
for avaliado comonull
, então nemA₀
nemA₁
serão avaliados. O mesmo será verdadeiro se uma expressão for uma sequência de operações null_conditional_member_access ou null_conditional_element_access§12.8.13.fim da observação
Um null_conditional_projection_initializer é uma restrição de null_conditional_member_access e tem as mesmas semânticas. Ela ocorre apenas como um inicializador de projeção em uma expressão de criação de objeto anônimo (§12.8.17.3).
12.8.9 Expressões tolerantes a valores nulos
12.8.9.1 Geral
O valor, o tipo, a classificação (§12.2) e o contexto seguro (§16.4.15) de uma expressão que permite valor nulo é o valor, o tipo, a classificação e o contexto seguro de sua primary_expression.
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
Observação: os operadores de perdão nulo e de negação lógica de prefixo (§12.9.4), embora representados pelo mesmo token léxico (!
), são distintos. Somente este último pode ser substituído (§15.10), a definição do operador que permite valor nulo é fixa.
fim da observação
É um erro em tempo de compilação aplicar o operador null-forgiving a valores nulos mais de uma vez à mesma expressão, independentemente de parênteses intervenientes.
Exemplo: todos os seguintes são inválidos:
var p = q!!; // error: applying null_forgiving_operator more than once var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)
fim do exemplo
O restante desta subcláusula e as subcláusulas irmãs a seguir são condicionalmente normativas.
Um compilador que executa a análise de estado nulo estático (§8.9.5) deve estar em conformidade com a especificação a seguir.
O operador tolerante a valores nulos é uma pseudo-operação em tempo de compilação usada para informar a análise de estado nulo estático do compilador. Ele tem dois usos: para substituir a determinação de um compilador de que uma expressão talvez seja nula; e para impedir que um compilador emita um aviso relacionado à nulidade.
Aplicar o operador tolerante a nulos a uma expressão para a qual a análise de estado nulo estático de um compilador não gera avisos não é um erro.
12.8.9.2 Substituindo uma determinação "talvez nula"
Em algumas circunstâncias, a análise de estado nulo estático de um compilador pode determinar que uma expressão tem o estado nulo talvez nulo e emitir um aviso de diagnóstico quando outras informações indicarem que a expressão não pode ser nula. Aplicar o operador forgiving nulo a tal expressão informa à análise de estado nulo estático do compilador de que o estado nulo está em não é nulo; o que impede o aviso de diagnóstico e pode informar qualquer análise em andamento.
Exemplo: Considere o seguinte:
#nullable enable public static void M() { Person? p = Find("John"); // returns Person? if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); // p can't be null } } public static bool IsValid(Person? person) => person != null && person.Name != null;
Se
IsValid
retornartrue
,p
poderá ser desreferenciado com segurança para acessar sua propriedadeName
e o aviso de "desreferenciamento de um valor possivelmente nulo" poderá ser suprimido usando!
.fim do exemplo
Exemplo: o operador tolerante a valores nulos deve ser usado com cuidado. Considere:
#nullable enable int B(int? x) { int y = (int)x!; // quash warning, throw at runtime if x is null return y; }
Aqui, o operador tolerante a valores nulos é aplicado a um tipo de valor e anula qualquer aviso sobre
x
. No entanto, sex
fornull
em tempo de execução, uma exceção será lançada, poisnull
não poderá ser convertido emint
.fim do exemplo
12.8.9.3 Substituindo outros avisos de análise nula
Além de substituir as determinações de talvez nulo, conforme mencionado acima, pode haver outras circunstâncias em que se deseja anular a determinação de análise de estado nulo estático de um compilador quando uma expressão requer um ou mais avisos. Aplicar o operador que ignora nulos a uma expressão solicita que o compilador não emita avisos para a expressão. Em resposta, um compilador pode optar por não emitir avisos e também pode modificar sua análise adicional.
Exemplo: Considere o seguinte:
#nullable enable public static void Assign(out string? lv, string? rv) { lv = rv; } public string M(string? t) { string s; Assign(out s!, t ?? "«argument was null»"); return s; }
Os tipos dos parâmetros do método
Assign
,lv
&rv
, sãostring?
, comlv
sendo um parâmetro de saída, e ele executa uma atribuição simples.O método
M
passa a variávels
, do tipostring
, como parâmetro de saída deAssign
, e o compilador usado emite um aviso, poiss
não é uma variável anulável. Considerando-se que o segundo argumento deAssign
não pode ser nulo, o operador null the null-forgiving é usado para anular o aviso.fim do exemplo
Fim do texto normativo condicional.
12.8.10 Expressões de invocação
12.8.10.1 Geral
invocation_expression é usado para invocar um método.
invocation_expression
: primary_expression '(' argument_list? ')'
;
A primary_expression pode ser uma null_forgiving_expression se e somente se possuir um delegate_type.
Uma invocation_expression será associada dinamicamente (§12.3.3) se pelo menos uma das seguintes condições for aplicável:
- A primary_expression tem tipo de tempo de compilação
dynamic
. - Pelo menos um argumento opcional da lista de argumentos tem tipo de tempo de compilação
dynamic
.
Nesse caso, o compilador classifica invocation_expression como um valor do tipo dynamic
. As regras abaixo para determinar o significado da invocation_expression são aplicadas em run-time, usando o tipo de run-time em vez do tipo de tempo de compilação da primary_expression e dos argumentos que têm o tipo de tempo de compilação dynamic
. Se a expressão primária não tiver tipo de tempo de compilação dynamic
, a chamada do método passará por uma verificação de tempo de compilação limitada, conforme descrito em §12.6.5.
A primary_expression de uma invocation_expression deve ser um grupo de métodos ou um valor de um delegate_type. Se primary_expression for um grupo de métodos, o invocation_expression será uma invocação de método (§12.8.10.2). Se primary_expression for um valor de delegate_type, o invocation_expression será uma invocação delegada (§12.8.10.4). Se a primary_expression não for um grupo de métodos nem um valor de um delegate_type, ocorrerá um erro de tempo de associação.
O argumento opcional argument_list (§12.6.2) fornece valores ou referências de variáveis para os parâmetros do método.
O resultado da avaliação de invocation_expression é classificado da seguinte maneira:
- Se o invocation_expression invocar um método sem valor de retorno (§15.6.1) ou um delegado sem valor de retorno, o resultado é inexistente. Uma expressão classificada como nada é permitida somente no contexto de statement_expression (§13.7) ou como o corpo de lambda_expression (§12.19). Caso contrário, ocorrerá um erro de tempo de associação.
- Caso contrário, se invocation_expression invoca um método returns-by-ref (§15.6.1) ou um delegado returns-by-ref, o resultado será uma variável com um tipo associado do tipo return do método ou delegado. Se a invocação for de um método de instância e o receptor for de um tipo de classe
T
, o tipo associado será escolhido na primeira declaração ou substituição do método encontrado ao começar comT
e pesquisar por meio das classes base. - Caso contrário, a expressão de invocação invoca um método que retorna por valor (§15.6.1) ou um delegado que retorna por valor, e o resultado é um valor, com um tipo associado ao tipo de retorno do método ou delegado. Se a invocação for de um método de instância e o receptor for de um tipo de classe
T
, o tipo associado será escolhido na primeira declaração ou substituição do método encontrado ao começar comT
e pesquisar por meio das classes base.
12.8.10.2 Invocações de método
Para uma invocação de método, primary_expression de invocation_expression deve ser um grupo de métodos. O grupo de métodos identifica o único método a ser invocado ou o conjunto de métodos sobrecarregados do qual escolher um método específico a ser invocado. Neste último caso, a determinação do método específico a ser invocado baseia-se no contexto fornecido pelos tipos dos argumentos em argument_list.
O processamento de tempo de associação de uma invocação de método do formato M(A)
, em que M
é um grupo de métodos (possivelmente incluindo uma type_argument_list), e A
é uma argument_listopcional, consiste nas seguintes etapas:
- O conjunto de métodos candidatos para a invocação do método é construído. Para cada método
F
associado ao grupo de métodosM
:- Se
F
não for genérico,F
será um candidato quando:-
M
não tem nenhuma lista de argumentos de tipo e -
F
é aplicável aoA
(§12.6.4.2).
-
- Se
F
for genérico eM
não tiver nenhuma lista de argumentos de tipo,F
será um candidato quando:- A inferência de tipo (§12.6.3) é bem-sucedida, inferindo uma lista de argumentos de tipo para a chamada e
- Depois que os argumentos de tipo inferidos são substituídos pelos parâmetros de tipo de método correspondentes, todos os tipos construídos na lista de parâmetros de
F
atendem às suas restrições (§8.4.5) e a lista de parâmetros deF
é aplicável em relação aA
(§12.6.4.2)
- Se
F
for genérico eM
incluir uma lista de argumentos de tipo,F
será um candidato quando:-
F
tem o mesmo número de parâmetros de tipo de método que foram fornecidos na lista de argumentos de tipo e - Depois que os argumentos de tipo são substituídos pelos parâmetros de tipo de método correspondentes, todos os tipos construídos na lista de parâmetros de
F
atendem às suas restrições (§8.4.5) e a lista de parâmetros deF
é aplicável em relação aA
(§12.6.4.2).
-
- Se
- O conjunto de métodos candidatos é reduzido para conter apenas métodos dos tipos mais derivados: para cada método
C.F
no conjunto, em queC
é o tipo no qual o métodoF
é declarado, todos os métodos declarados em um tipo base deC
são removidos do conjunto. Além disso, seC
for um tipo de classe diferente deobject
, todos os métodos declarados em um tipo de interface serão removidos do conjunto.Observação: esta última regra só tem efeito quando o grupo de métodos for o resultado de uma pesquisa de membro em um parâmetro de tipo com uma classe base efetiva diferente de
object
e um conjunto de interfaces efetivas não vazio. fim da observação - Se o conjunto resultante de métodos candidatos estiver vazio, o processamento adicional ao longo das etapas a seguir será abandonado e, em vez disso, será feita uma tentativa de processar a invocação como uma invocação do método de extensão (§12.8.10.3). Se isso falhar, nenhum método aplicável existirá e ocorrerá um erro de tempo de associação.
- O melhor método do conjunto de métodos candidatos é identificado usando as regras de resolução de sobrecarga de §12.6.4. Se um único método melhor não puder ser identificado, a invocação do método será ambígua e ocorrerá um erro de tempo de associação. Ao executar a resolução de sobrecarga, os parâmetros de um método genérico são considerados depois de substituir os argumentos de tipo (fornecidos ou inferidos) pelos parâmetros de tipo de método correspondentes.
Depois que um método tiver sido selecionado e validado em tempo de vinculação pelas etapas acima, a invocação em tempo de execução real será processada de acordo com as regras de invocação do membro de função descritas em §12.6.6.
Observação: O efeito intuitivo das regras de resolução descritas acima é o seguinte: para localizar o método específico invocado por uma chamada de método, comece com o tipo indicado pela chamada de método e prossiga subindo a cadeia de herança até encontrar pelo menos uma declaração de método aplicável, acessível e que não substitua. Em seguida, execute a inferência de tipo e a resolução de sobrecarga no conjunto de métodos aplicáveis, acessíveis e não substituídos declarados nesse tipo, e invoque o método assim selecionado. Se nenhum método foi encontrado, tente processar a invocação como uma invocação de método de extensão. fim da observação
12.8.10.3 Invocações de método de extensão
Em uma invocação de método (§12.6.6.2) de uma das formas
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
se o processamento normal da invocação não encontrar métodos aplicáveis, será feita uma tentativa de processar o constructo como uma invocação de método de extensão. Se «expr» ou qualquer um dos «args» tiver dynamic
de tipo de tempo de compilação, os métodos de extensão não serão aplicados.
O objetivo é encontrar o melhor type_nameC
, para que a invocação do método estático correspondente possa ocorrer:
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Um método de extensão Cᵢ.Mₑ
será qualificado se:
-
Cᵢ
é uma classe não genérica, não aninhada - O nome de
Mₑ
é identificador -
Mₑ
é acessível e aplicável quando aplicado aos argumentos como um método estático, conforme mostrado acima - Existe uma conversão implícita de identidade, referência ou boxing de expr para o tipo do primeiro parâmetro de
Mₑ
.
A pesquisa para C
é a seguinte:
- Começando com a declaração de namespace mais próxima, continuando com cada declaração de namespace envolvente e terminando com a unidade de compilação que a contém, são feitas tentativas sucessivas para encontrar um conjunto candidato de métodos de extensão:
- Se o namespace ou a unidade de compilação fornecido contiver diretamente declarações de tipo não genérico
Cᵢ
com métodos de extensão qualificadosMₑ
, o conjunto desses métodos de extensão será o conjunto de candidatos. - Se os namespaces importados usando diretivas de namespace no namespace fornecido ou na unidade de compilação contiverem diretamente declarações de tipo não genérico
Cᵢ
com métodos de extensão qualificadosMₑ
, o conjunto desses métodos de extensão será o conjunto de candidatos.
- Se o namespace ou a unidade de compilação fornecido contiver diretamente declarações de tipo não genérico
- Se nenhum conjunto de candidatos for encontrado em nenhuma declaração de namespace ou unidade de compilação, ocorrerá um erro de tempo de compilação.
- Caso contrário, a resolução de sobrecarga será aplicada ao conjunto de candidatos, conforme descrito em §12.6.4. Se nenhum método melhor for encontrado, ocorrerá um erro de tempo de compilação.
-
C
é o tipo no qual o melhor método é declarado como um método de extensão.
Usando-se C
como destino, a chamada de método é processada como uma invocação de método estático (§12.6.6).
Observação: ao contrário de uma invocação de método de instância, nenhuma exceção é gerada quando expr é avaliada como uma referência nula. Em vez disso, esse valor
null
é passado para o método de extensão da mesma forma que seria em uma invocação regular de método estático. Cabe à implementação do método de extensão decidir como responder a essa chamada. fim da observação
As regras anteriores significam que os métodos de instância têm precedência sobre métodos de extensão, que os métodos de extensão disponíveis em declarações de namespace interno têm precedência sobre os métodos de extensão disponíveis em declarações de namespace externos e que os métodos de extensão declarados diretamente em um namespace têm precedência sobre os métodos de extensão importados para esse mesmo namespace com uma diretiva de namespace usando.
Exemplo:
public static class E { public static void F(this object obj, int i) { } public static void F(this object obj, string s) { } } class A { } class B { public void F(int i) { } } class C { public void F(object obj) { } } class X { static void Test(A a, B b, C c) { a.F(1); // E.F(object, int) a.F("hello"); // E.F(object, string) b.F(1); // B.F(int) b.F("hello"); // E.F(object, string) c.F(1); // C.F(object) c.F("hello"); // C.F(object) } }
No exemplo, o método de
B
tem precedência sobre o primeiro método de extensão e o método deC
tem precedência sobre ambos os métodos de extensão.public static class C { public static void F(this int i) => Console.WriteLine($"C.F({i})"); public static void G(this int i) => Console.WriteLine($"C.G({i})"); public static void H(this int i) => Console.WriteLine($"C.H({i})"); } namespace N1 { public static class D { public static void F(this int i) => Console.WriteLine($"D.F({i})"); public static void G(this int i) => Console.WriteLine($"D.G({i})"); } } namespace N2 { using N1; public static class E { public static void F(this int i) => Console.WriteLine($"E.F({i})"); } class Test { static void Main(string[] args) { 1.F(); 2.G(); 3.H(); } } }
A saída deste exemplo é:
E.F(1) D.G(2) C.H(3)
D.G
tem precedência sobreC.G
eE.F
tem precedência sobreD.F
eC.F
.fim do exemplo
12.8.10.4 Invocações de delegado
Para uma invocação de delegado, a primary_expression da invocation_expression deve ser um valor de um delegate_type. Além disso, considerando delegate_type ser um membro de função com a mesma lista de parâmetros que o delegate_type, delegate_type deve ser aplicável (§12.6.4.2) em relação a argument_list de invocation_expression.
O processamento em run-time de uma invocação de delegado do formato D(A)
, em que D
é uma primary_expression de um delegate_type e A
é uma argument_listopcional, consiste nas seguintes etapas:
-
D
é avaliado. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. - A lista de argumentos
A
é avaliada. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. - O valor de
D
é verificado como sendo válido. Se o valor deD
fornull
,System.NullReferenceException
será lançado e nenhuma etapa adicional será executada. - Caso contrário,
D
é uma referência a uma instância delegada. Invocações de membro de função (§12.6.6) são executadas em cada uma das entidades que podem ser chamadas na lista de invocação de delegado. Para entidades chamáveis que consistem de uma instância e um método de instância, a instância para a invocação é a instância contida na entidade chamável.
Consulte §20.6 para obter detalhes de várias listas de invocação sem parâmetros.
12.8.11 Expressão de invocação condicional nula
Uma null_conditional_invocation_expression é sintaticamente um null_conditional_member_access (§12.8.8) ou um null_conditional_element_access (§12.8.13) onde o dependent_access final é uma expressão de invocação (§12.8.10).
Uma null_conditional_invocation_expression ocorre no contexto de uma statement_expression (§13.7), um anonymous_function_body (§12.19.1) ou um method_body (§15.6.1).
Ao contrário do null_conditional_member_access ou null_conditional_element_accesssintaticamente equivalente, uma null_conditional_invocation_expression pode ser classificada como nada.
null_conditional_invocation_expression
: null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
| null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
;
O null_forgiving_operator opcional poderá ser incluído se e somente se o null_conditional_member_access ou null_conditional_element_access tiver um delegate_type.
Uma expressão null_conditional_invocation_expressionE
é do formato P?A
; onde A
é o resto do null_conditional_member_access ou null_conditional_element_access sintaticamente equivalente, portanto, A
começará com .
ou [
. Deixe PA
indicar a concatenação de P
e A
.
Quando E
ocorre como statement_expression o significado de E
é o mesmo que o significado da instrução :
if ((object)P != null) PA
exceto que P
é avaliado apenas uma vez.
Quando E
ocorre como anonymous_function_body ou method_body o significado de E
depende de sua classificação:
Se
E
for classificado como nada, seu significado será o mesmo que o significado do bloco :{ if ((object)P != null) PA; }
exceto que
P
é avaliado apenas uma vez.Caso contrário, o significado de
E
é o mesmo que o significado do bloco :{ return E; }
e, por sua vez, o significado desse bloco depende se
E
é sintaticamente equivalente a null_conditional_member_access (§12.8.8) or null_conditional_element_access (§12.8.13).
12.8.12 Acesso ao elemento
12.8.12.1 Geral
Um element_access consiste em um primary_expression, seguido por um token "[
", seguido por um argument_list, seguido por um token "]
".
argument_list consiste em um ou mais argumentos separados por vírgulas.
element_access
: primary_expression '[' argument_list ']'
;
Ao reconhecer um primary_expression se as alternativas element_access e pointer_element_access (§23.6.4) forem aplicáveis, a última será escolhida se o primary_expression inserido for do tipo de ponteiro (§23.3).
O argument_list de um element_access não deve conter out
ou ref
argumentos.
O primary_expression de um element_access não deve ser um array_creation_expression a menos que inclua um array_initializer ou um stackalloc_expression , a menos que inclua um stackalloc_initializer.
Observação: essa restrição existe para não permitir código potencialmente confuso, como:
var a = new int[3][1];
que, de outra forma, seria interpretado como:
var a = (new int[3])[1];
Uma restrição semelhante se aplica a null_conditional_element_access (§12.8.13). fim da observação
Um element_access será associado dinamicamente (§12.3.3) se pelo menos uma das seguintes condições for aplicável:
- A primary_expression tem tipo de tempo de compilação
dynamic
. - Pelo menos uma expressão da argument_list tem tipo de tempo de compilação
dynamic
e a primary_expression não tem tipo de array.
Nesse caso, o compilador classifica element_access como um valor do tipo dynamic
. As regras abaixo para determinar o significado do element_access são então aplicadas em tempo de execução, usando o tipo de tempo de execução em vez do tipo de tempo de compilação das expressões primary_expression e argument_list que têm o tipo dynamic
de tempo de compilação. Se o primary_expression não tiver o tipo dynamic
de tempo de compilação, o acesso ao elemento passará por uma verificação de tempo de compilação limitada, conforme descrito em §12.6.5.
Se a primary_expression de um element_access for um valor de tipo_de_array, o element_access será um acesso de array (§12.8.12.2). Caso contrário, o primary_expression será uma variável ou valor de uma classe, struct ou tipo de interface que tenha um ou mais membros indexador, nesse caso, o element_access é um acesso de indexador (§12.8.12.3).
12.8.12.2 Acesso de matriz
Para um acesso de matriz, a primary_expression do element_access deve ser um valor de um tipo de array. Além disso, o argument_list de um acesso de matriz não deve conter argumentos nomeados. O número de expressões em argument_list deve ser igual à classificação de array_type, e cada expressão deve ser do tipo int
, uint
, long
ou ulong,
ou deve ser implicitamente conversível para um ou mais desses tipos.
O resultado da avaliação de um acesso de matriz é uma variável do tipo de elemento da matriz, ou seja, o elemento de matriz selecionado pelos valores das expressões no argument_list.
O processamento em tempo de execução de um acesso a matriz do formulário P[A]
, onde P
é uma primary_expression de um array_type e A
é uma argument_list, consiste nas seguintes etapas:
-
P
é avaliado. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. - As expressões de índice de argument_list são avaliadas em ordem, da esquerda para a direita. Após a avaliação de cada expressão de índice, uma conversão implícita (§10.2) para um dos seguintes tipos é executada:
int
,uint
,long
,ulong
. O primeiro tipo nessa lista para o qual existe uma conversão implícita é escolhido. Por exemplo, se a expressão de índice for do tiposhort
uma conversão implícita emint
será executada, uma vez que são possíveis conversões implícitas deshort
paraint
e deshort
paralong
. Se a avaliação de uma expressão de índice ou a conversão implícita subsequente causar uma exceção, nenhuma expressão de índice adicional será avaliada e nenhuma outra etapa será executada. - O valor de
P
é verificado como sendo válido. Se o valor deP
fornull
,System.NullReferenceException
será lançado e nenhuma etapa adicional será executada. - O valor de cada expressão em argument_list é verificado em relação aos limites reais de cada dimensão da instância da matriz referenciada por
P
. Se um ou mais valores estiverem fora do intervalo,System.IndexOutOfRangeException
será lançado e nenhuma etapa adicional será executada. - O local do elemento de matriz fornecido pelas expressões de índice é computado e esse local se torna o resultado do acesso à matriz.
12.8.12.3 Acesso de indexador
Para um acesso de indexador, o primary_expression do element_access deve ser uma variável ou valor de uma classe, struct ou tipo de interface, e esse tipo deve implementar um ou mais indexadores aplicáveis em relação ao argument_list do element_access.
O processamento em tempo de vinculação de um acesso de indexador do formulário P[A]
, onde P
é uma primary_expression de uma classe, struct ou tipo de interface T
, e A
é uma argument_list, consiste nas seguintes etapas:
- O conjunto de indexadores fornecido por
T
foi construído. O conjunto consiste em todos os indexadores declarados emT
ou um tipo base deT
que não são declarações de substituição e podem ser acessados no contexto atual (§7.5). - O conjunto é reduzido aos indexadores aplicáveis e não ocultos por outros indexadores. As regras a seguir são aplicadas a cada indexador
S.I
no conjunto, em queS
é o tipo no qual o indexadorI
é declarado:- Se
I
não for aplicável em relação aoA
(§12.6.4.2),I
será removido do conjunto. - Se
I
for aplicável em relação aA
(§12.6.4.2), todos os indexadores declarados em um tipo base deS
serão removidos do conjunto. - Se
I
for aplicável em relação aA
(§12.6.4.2) eS
for um tipo de classe diferente deobject
, todos os indexadores declarados em uma interface serão removidos do conjunto.
- Se
- Se o conjunto resultante de indexadores candidatos estiver vazio, nenhum indexador aplicável existirá e ocorrerá um erro de tempo de associação.
- O melhor indexador do conjunto de indexadores candidatos é identificado usando as regras de resolução de sobrecarga de §12.6.4. Se um único melhor indexador não puder ser identificado, o acesso ao indexador será ambíguo e ocorrerá um erro de tempo de associação.
- As expressões de índice de argument_list são avaliadas em ordem, da esquerda para a direita. O resultado do processamento do acesso do indexador é uma expressão classificada como um acesso de indexador. A expressão de acesso do indexador faz referência ao indexador determinado na etapa acima e tem uma expressão de instância associada de
P
e uma lista de argumentos associada deA
e um tipo associado que é o tipo do indexador. SeT
for um tipo de classe, o tipo associado será escolhido na primeira declaração ou substituição de indexador encontrado ao começar comT
e pesquisar por meio de suas classes base.
Dependendo do contexto no qual ele é usado, um acesso de indexador causa a invocação do acessador get ou do acessador set do indexador. Se o acesso do indexador for o destino de uma atribuição, o acessador set será invocado para atribuir um novo valor (§12.21.2). Em todos os outros casos, o acessador get é invocado para obter o valor atual (§12.2.2).
12.8.13 Acesso de elemento condicional nulo
Um null_conditional_element_access consiste em um primary_expression, seguido pelos dois tokens "?
" e "[
", seguido por um argument_list, seguido por um token "]
", seguido por zero ou mais dependent_access, dos quais qualquer um pode ser precedido por um null_forgiving_operator.
null_conditional_element_access
: primary_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
O argument_list de um null_conditional_element_access não deve conter out
ou ref
argumentos.
O primary_expression de um null_conditional_element_access não deve ser um array_creation_expression a menos que inclua um array_initializer, ou um stackalloc_expression a menos que inclua um stackalloc_initializer.
Observação: essa restrição existe para não permitir código potencialmente confuso. Uma restrição semelhante se aplica a element_access (§12.8.12) em que um exemplo do que é excluído pode ser encontrado. fim da observação
Um null_conditional_element_access é uma versão condicional do element_access (§12.8.12) e será um erro de tempo de associação se o tipo de resultado for void
. Para uma expressão condicional nula em que o tipo de resultado pode ser void
consulte (§12.8.11).
Uma expressão null_conditional_element_accessE
é do formato P?[A]B
; onde B
são os dependent_accesses, se houver. O significado de E
é determinado da seguinte maneira:
Se o tipo de
P
for um tipo de valor anulável:Seja
T
o tipo da expressãoP.Value[A]B
.Se
T
for um parâmetro de tipo que não é conhecido por ser um tipo de referência ou um tipo de valor não anulável, ocorrerá um erro de tempo de compilação.Se
T
for um tipo de valor não anulável, o tipo deE
seráT?
e o significado deE
será o mesmo que o significado de:((object)P == null) ? (T?)null : P.Value[A]B
Exceto que
P
é avaliado apenas uma vez.Caso contrário, o tipo de
E
éT
e o significado deE
é o mesmo que o significado de:((object)P == null) ? null : P.Value[A]B
Exceto que
P
é avaliado apenas uma vez.
Ou:
Seja
T
o tipo da expressãoP[A]B
.Se
T
for um parâmetro de tipo que não é conhecido por ser um tipo de referência ou um tipo de valor não anulável, ocorrerá um erro de tempo de compilação.Se
T
for um tipo de valor não anulável, o tipo deE
seráT?
e o significado deE
será o mesmo que o significado de:((object)P == null) ? (T?)null : P[A]B
Exceto que
P
é avaliado apenas uma vez.Caso contrário, o tipo de
E
éT
e o significado deE
é o mesmo que o significado de:((object)P == null) ? null : P[A]B
Exceto que
P
é avaliado apenas uma vez.
Observação: em uma expressão do formato:
P?[A₀]?[A₁]
se
P
for avaliado comonull
, nemA₀
nemA₁
são avaliados. O mesmo será verdadeiro se uma expressão for uma sequência de operações null_conditional_element_access ou null_conditional_member_access§12.8.8.fim da observação
12.8.14 Este acesso
Um this_access consiste na palavra chave this
.
this_access
: 'this'
;
Um this_access é permitido somente no bloco de um construtor de instância, um método de instância, um acessador de instância (§12.2.1) ou um finalizador. Ele tem um dos seguintes significados:
- Quando
this
é usado em uma expressão_primária dentro de um construtor de instância de uma classe, ele é classificado como um valor. O tipo do valor é o tipo de instância (§15.3.2) da classe na qual o uso ocorre e o valor é uma referência ao objeto que está sendo construído. - Quando
this
é usado em primary_expression em um método de instância ou acessador de instância de uma classe, ele é classificado como um valor. O tipo do valor é o tipo de instância (§15.3.2) da classe na qual o uso ocorre e o valor é uma referência ao objeto para o qual o método ou acessador foi invocado. - Quando
this
é usado em uma primary_expression em um construtor de instância de uma estrutura, ele é classificado como uma variável. O tipo da variável é o tipo de instância (§15.3.2) do struct no qual o uso ocorre e a variável representa o struct que está sendo construído.- Se a declaração do construtor não tiver nenhum inicializador de construtor, a variável
this
se comportará exatamente como um parâmetro de saída do tipo struct. Em particular, isso significa que a variável deve ser definitivamente atribuída em cada caminho de execução do construtor de instância. - Caso contrário, a variável
this
se comportará exatamente como um parâmetroref
do tipo struct. Em particular, isso significa que a variável é considerada inicialmente atribuída.
- Se a declaração do construtor não tiver nenhum inicializador de construtor, a variável
- Quando
this
é usado em uma primary_expression em um método de instância ou acessador de instância de uma estrutura, ele é classificado como uma variável. O tipo da variável é o tipo de instância (§15.3.2) do struct no qual o uso ocorre.- Se o método ou acessador não for um iterador (§15.15) ou uma função assíncrona (§15.14), a
this
variável representará o struct para o qual o método ou acessador foi invocado.- Se o struct for um
readonly struct
, a variávelthis
se comportará exatamente como um parâmetro de entrada do tipo struct - Caso contrário, a variável
this
se comportará exatamente como um parâmetroref
do tipo struct
- Se o struct for um
- Se o método ou acessador for uma função iterador ou assíncrona, a variável
this
representará uma cópia do struct para o qual o método ou acessador foi invocado e se comportará exatamente da mesma forma que um parâmetro do valor do tipo struct.
- Se o método ou acessador não for um iterador (§15.15) ou uma função assíncrona (§15.14), a
O uso de this
em primary_expression em um contexto diferente daqueles listados acima é um erro de tempo de compilação. Em particular, não é possível fazer referência a this
em um método estático, acessador de propriedade estático ou em variable_initializer de uma declaração de campo.
12.8.15 Acesso base
base_access consiste na palavra-chave base
seguida por um token “.
”, um identificado e type_argument_list opcional ou argument_list entre colchetes:
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
Um base_access é usado para acessar membros da classe base que estão ocultos por membros com nomes semelhantes na classe ou estrutura atual. Um base_access é permitido somente no corpo de um construtor de instância, um método de instância, um acessador de instância (§12.2.1) ou um finalizador. Quando base.I
ocorre em uma classe ou struct, I
deve indicar um membro da classe base dessa classe ou struct. Da mesma forma, quando base[E]
ocorre em uma classe, um indexador aplicável deve existir na classe base.
Em tempo de associação, as expressões base_access do formato base.I
e base[E]
são avaliadas exatamente como se fossem escritas ((B)this).I
e ((B)this)[E]
, em que B
é a classe base da classe ou estrutura na qual a construção ocorre. Assim, base.I
e base[E]
correspondem a this.I
e this[E]
, exceto que this
é exibido como uma instância da classe base.
Quando base_access faz referência a um membro de função virtual (um método, propriedade ou indexador), a determinação de qual membro de função invocar em run-time (§12.6.6) é alterada. O membro da função invocado é determinado pela localização da implementação mais derivada (§15.6.4) do membro da função em relação a B
(em vez de em relação ao tipo de run-time de this
, como seria de costume em um acesso não base). Portanto, em uma substituição de um membro de função virtual, um base_access pode ser usado para invocar a implementação herdada do membro da função. Se o membro da função referenciado por um base_access for abstrato, ocorrerá um erro de tempo de associação.
Observação: ao contrário de
this
,base
não é uma expressão em si. É uma palavra-chave usada apenas no contexto de base_access ou constructor_initializer (§15.11.2). fim da observação
12.8.16 Operadores de incremento e de decremento pós-fixados
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
O operando de uma operação de adição ou remoção pós-fixada deve ser uma expressão classificada como uma variável, um acesso de propriedade ou um acesso de indexador. O resultado da operação é um valor do mesmo tipo do operando.
Se primary_expression tiver o tipo de tempo de compilação dynamic
, o operador será associado dinamicamente (§12.3.3), post_increment_expression ou post_decrement_expression terá o tipo de tempo de compilação dynamic
e as regras a seguir serão aplicadas em run-time usando-se o tipo de run-time de primary_expression.
Se o operando de uma operação de adição ou remoção pós-fixada for um acesso de propriedade ou indexador, a propriedade ou o indexador deverá ter acessores get e set. Se esse não for o caso, ocorrerá um erro de tempo de associação.
A resolução de sobrecarga de operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. Há operadores predefinidos ++
e --
para os seguintes tipos: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
e qualquer tipo de enumeração. Os operadores predefinidos ++
retornam o valor produzido adicionando-se 1
ao operando e os operadores predefinidos --
retornam o valor produzido pela subtração de 1
do operando. Em um contexto verificado, se o resultado dessa adição ou subtração estiver fora do intervalo do tipo de resultado e o tipo de resultado for um tipo integral ou tipo de enumeração, System.OverflowException
será lançado.
Deve haver uma conversão implícita do tipo de retorno do operador unário selecionado para o tipo primary_expression; caso contrário, ocorrerá um erro de tempo de compilação.
O processamento run-time de uma operação de incremento ou decremento pós-fixada no formato x++
ou x--
consiste nas seguintes etapas:
- Se
x
for classificado como uma variável:-
x
é avaliado para produzir a variável. - O valor de
x
é armazenado. - O valor salvo
x
é convertido no tipo de operando do operador selecionado e o operador é invocado com esse valor como seu argumento. - O valor retornado pelo operador é convertido no tipo
x
e armazenado no local fornecido pela avaliação anterior dex
. - O valor salvo
x
torna-se o resultado da operação.
-
- Se
x
for classificado como uma propriedade ou acesso de indexador:- A expressão de instância (se
x
não forstatic
) e a lista de argumentos (sex
for um acesso de indexador) associadas ax
serão avaliadas e os resultados serão usados nas invocações subsequentes do acessador get e set. - O acessador get de
x
é invocado e o valor retornado é salvo. - O valor salvo
x
é convertido no tipo de operando do operador selecionado e o operador é invocado com esse valor como seu argumento. - O valor retornado pelo operador é convertido no tipo
x
e o acessador set dex
é invocado com esse valor como seu argumento de valor. - O valor salvo
x
torna-se o resultado da operação.
- A expressão de instância (se
Os operadores ++
e --
também dão suporte à notação de pré-fixada (§12.9.6). O resultado de x++
ou x--
é o valor de x
antes da operação, enquanto o resultado de ++x
ou --x
for o valor de x
após a operação. Nos dois casos, x
em si tem o mesmo valor após a operação.
Uma implementação do operador ++
ou do operador --
pode ser invocada usando a notação pós-fixada ou pré-fixada. Não é possível ter implementações de operador separadas para as duas notações.
12.8.17 O operador new
12.8.17.1 Geral
O operador new
é usado para criar novas instâncias de tipos.
Há três formas de novas expressões:
- Expressões de criação de objeto são usadas para criar novas instâncias de tipos de classe e tipos de valor.
- As expressões de criação de matriz são usadas para criar novas instâncias de tipos de matriz.
- Expressões de criação de delegado são usadas para obter instâncias de tipos de delegado.
O operador new
implica na criação de uma instância de um tipo, mas não implica necessariamente a alocação de memória. Em particular, instâncias de tipos de valor não exigem memória adicional além das variáveis em que residem e nenhuma alocação ocorre quando usa-se new
para criar as instâncias de tipos de valor.
Observação: expressões de criação de delegado nem sempre criam novas instâncias. Quando a expressão é processada da mesma forma que uma conversão de grupo de métodos (§10.8) ou uma conversão de função anônima (§10.7), isso pode resultar em uma instância de delegado existente sendo reutilizada. fim da observação
12.8.17.2 Expressões de criação de objeto
12.8.17.2.1 Geral
object_creation_expression é usado para criar uma nova instância de class_type ou value_type.
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
;
object_or_collection_initializer
: object_initializer
| collection_initializer
;
O tipo de uma object_creation_expression deve ser um class_type, um value_typeou um type_parameter. O tipo não pode ser um tuple_type ou um class_type abstrato ou estático.
A argument_list opcional (§12.6.2) só será permitida se o tipo for um class_type ou um struct_type.
Uma expressão de criação de objeto pode omitir a lista de argumentos do construtor e incluir parênteses, desde que inclua um inicializador de objeto ou inicializador de coleção. Omitir a lista de argumentos do construtor e os parênteses é equivalente a especificar uma lista de argumentos vazia.
O processamento de uma expressão de criação de objeto que inclui um inicializador de objeto ou inicializador de coleção consiste primeiro em processar o construtor de instância e, em seguida, processar as inicializações de membro ou elemento especificadas pelo inicializador de objeto (§12.8.17.2.2) ou inicializador de coleção (§12.8.17.2.3).
Se qualquer um dos argumentos em argument_list opcional tiver o tipo de tempo de compilação dynamic
, object_creation_expression será associado dinamicamente (§12.3.3) e as regras a seguir serão aplicadas em run-time usando o tipo de run-time desses argumentos de argument_list que têm o tipo de tempo de compilação dynamic
. No entanto, a criação do objeto passa por uma verificação de tempo de compilação limitada, conforme descrito em §12.6.5.
O processamento de object_creation_expression do formato new T(A)
, em que T
é um class_type ou value_type e A
é argument_list opcional, consiste nas seguintes etapas:
- Se
T
for um value_type eA
não estiver presente:-
object_creation_expression é uma invocação de construtor padrão. O resultado do object_creation_expression é um valor do tipo
T
, ou seja, o valor padrão paraT
conforme definido em §8.3.3.
-
object_creation_expression é uma invocação de construtor padrão. O resultado do object_creation_expression é um valor do tipo
- Caso contrário, se
T
for um type_parameter eA
não estiver presente:- Se nenhuma restrição de tipo de valor ou restrição de construtor (§15.2.5) tiver sido especificada para
T
, ocorrerá um erro de tempo de associação. - O resultado de object_creation_expression é um valor do tipo de run-time ao qual o parâmetro de tipo foi associado, ou seja, o resultado de invocar o construtor padrão desse tipo. O tipo de run-time pode ser um tipo de referência ou valor.
- Se nenhuma restrição de tipo de valor ou restrição de construtor (§15.2.5) tiver sido especificada para
- Caso contrário, se
T
for um class_type ou um struct_type:- Se
T
for class_type abstrato ou estático, ocorrerá um erro de tempo de compilação. - O construtor de instância a ser invocado é determinado usando-se as regras de resolução de sobrecarga de §12.6.4. O conjunto de construtores de instâncias candidatos consiste em todos os construtores de instância acessível declarados em
T
, que são aplicáveis em relação aA
(§12.6.4.2). Se o conjunto de construtores de instâncias candidatos estiver vazio ou se um único melhor construtor de instância não puder ser identificado, ocorrerá um erro em tempo de ligação. - O resultado de object_creation_expression é um valor do tipo
T
, ou seja, o valor produzido invocando o construtor de instância determinado na etapa acima. - Caso contrário, object_creation_expression é inválido e ocorre um erro de tempo de associação.
- Se
Mesmo que object_creation_expression esteja associado dinamicamente, o tipo de tempo de compilação ainda será T
.
O processamento de run-time de uma object_creation_expression do novo formato T(A)
, em que T
é um class_type ou um struct_type e A
é uma argument_listopcional, consiste nas seguintes etapas:
- Se
T
for um class_type:- Uma nova instância da classe
T
é alocada. Se não houver memória suficiente disponível para alocar a nova instância,System.OutOfMemoryException
será gerado e nenhuma outra etapa será executada. - Todos os campos da nova instância são inicializados para seus valores padrão (§9.3).
- O construtor de instância é invocado de acordo com as regras de invocação de membro de função (§12.6.6). Uma referência à instância recém-alocada é passada automaticamente para o construtor de instância e a instância pode ser acessada dentro desse construtor.
- Uma nova instância da classe
- Se
T
for um struct_type:- Uma instância do tipo
T
é criada alocando-se uma variável local temporária. Como um construtor de instância de struct_type é necessário para atribuir definitivamente um valor a cada campo da instância que está sendo criada, nenhuma inicialização da variável temporária é necessária. - O construtor de instância é invocado de acordo com as regras de invocação de membro de função (§12.6.6). Uma referência à instância recém-alocada é passada automaticamente para o construtor de instância e a instância pode ser acessada dentro desse construtor.
- Uma instância do tipo
12.8.17.2.2 Inicializadores de objeto
Um inicializador de objeto especifica os valores para zero ou mais campos, propriedades ou elementos indexados de um objeto.
object_initializer
: '{' member_initializer_list? '}'
| '{' member_initializer_list ',' '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: initializer_target '=' initializer_value
;
initializer_target
: identifier
| '[' argument_list ']'
;
initializer_value
: expression
| object_or_collection_initializer
;
Um inicializador de objeto consiste em uma sequência de inicializadores de membro, delimitados pelos tokens {
e }
e separados por vírgulas. Cada member_initializer deve designar um destino para a inicialização. Um identificador deve denominar um campo acessível ou propriedade do objeto que esteja sendo inicializado, enquanto argument_list entre colchetes deve especificar argumentos para um indexador acessível no objeto que esteja sendo inicializado. É um erro que um inicializador de objeto inclua mais de um inicializador de membro para o mesmo campo ou propriedade.
Observação: embora um inicializador de objeto não tenha permissão para definir o mesmo campo ou propriedade mais de uma vez, não há essas restrições para os indexadores. Um inicializador de objeto pode conter vários destinos de inicializador que se referem a indexadores e pode até usar os mesmos argumentos do indexador várias vezes. fim da observação
Cada initializer_target é seguido por um sinal de igual e uma expressão, um inicializador de objeto ou um inicializador de coleção. Não é possível que expressões dentro do inicializador de objeto se refiram ao objeto recém-criado que ele está inicializando.
Um inicializador de membro que especifica uma expressão após o sinal de igual é processado da mesma forma que uma atribuição (§12.21.2) para o destino.
Um inicializador de membro que especifica um inicializador de objeto após o sinal de igual é um inicializador de objeto aninhado, ou seja, uma inicialização de um objeto integrado. Em vez de atribuir um novo valor ao campo ou à propriedade, as atribuições no inicializador de objeto aninhado são tratadas como atribuições para os membros do campo ou da propriedade. Inicializadores de objeto aninhados não podem ser aplicados a propriedades com um tipo de valor ou a campos somente leitura com um tipo de valor.
Um inicializador membro que especifica um inicializador de coleção após o sinal de igual é uma inicialização de uma coleção integrada. Em vez de atribuir uma nova coleção para o campo de destino, propriedade ou indexador, os elementos fornecidos no inicializador são adicionados à coleção referenciada pelo destino. O destino deve ser de um tipo de coleção que atenda aos requisitos especificados em §12.8.17.2.3.
Quando um destino de inicializador se refere a um indexador, os argumentos para o indexador sempre serão avaliados exatamente uma vez. Assim, mesmo que os argumentos acabem nunca sendo usados (por exemplo, devido a um inicializador aninhado vazio), eles são avaliados quanto aos efeitos colaterais.
Exemplo: a seguinte classe representa um ponto com duas coordenadas:
public class Point { public int X { get; set; } public int Y { get; set; } }
Uma instância de
Point
pode ser criada e inicializada da seguinte maneira:Point a = new Point { X = 0, Y = 1 };
Isso tem o mesmo efeito que
Point __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;
em que
__a
é uma variável temporária invisível e inacessível.A classe a seguir mostra um retângulo criado de dois pontos e a criação e a inicialização de uma instância de
Rectangle
:public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }
Uma instância de
Rectangle
pode ser criada e inicializada da seguinte maneira:Rectangle r = new Rectangle { P1 = new Point { X = 0, Y = 1 }, P2 = new Point { X = 2, Y = 3 } };
Isso tem o mesmo efeito que
Rectangle __r = new Rectangle(); Point __p1 = new Point(); __p1.X = 0; __p1.Y = 1; __r.P1 = __p1; Point __p2 = new Point(); __p2.X = 2; __p2.Y = 3; __r.P2 = __p2; Rectangle r = __r;
em que
__r
,__p1
e__p2
são variáveis temporárias que são invisíveis e inacessíveis.Se o construtor de
Rectangle
alocar as duas instâncias incorporadas dePoint
, elas poderão ser usadas para inicializar as instâncias incorporadas dePoint
em vez de atribuir novas instâncias.public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }
o constructo a seguir pode ser usado para inicializar as instâncias de
Point
inseridas em vez de atribuir novas instâncias:Rectangle r = new Rectangle { P1 = { X = 0, Y = 1 }, P2 = { X = 2, Y = 3 } };
Isso tem o mesmo efeito que
Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r;
fim do exemplo
12.8.17.2.3 Inicializadores de coleção
Um inicializador de coleção especifica os elementos de uma coleção.
collection_initializer
: '{' element_initializer_list '}'
| '{' element_initializer_list ',' '}'
;
element_initializer_list
: element_initializer (',' element_initializer)*
;
element_initializer
: non_assignment_expression
| '{' expression_list '}'
;
expression_list
: expression (',' expression)*
;
Um inicializador de coleção consiste em uma sequência de inicializadores de elemento, delimitados pelos tokens {
e }
e separados por vírgulas. Cada inicializador especifica um elemento a ser adicionado ao objeto de coleção que está sendo inicializado e consiste em uma lista de expressões entre os tokens {
e }
, separadas por vírgulas. Um inicializador de elemento de expressão única pode ser escrito sem chaves, mas não pode ser uma expressão de atribuição, para evitar ambiguidade com inicializadores de membros. A produção de non_assignment_expression é definida em §12.22.
Exemplo: a seguir há um exemplo de uma expressão de criação de objeto que inclui um inicializador de coleção:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
fim do exemplo
O objeto de coleção ao qual um inicializador de coleção é aplicado deve ser de um tipo que implementa System.Collections.IEnumerable
ou ocorre um erro de tempo de compilação. Para cada elemento especificado em ordem da esquerda para a direita, a pesquisa de membro normal é aplicada para localizar um membro chamado Add
. Se o resultado da pesquisa de membro não for um grupo de métodos, ocorrerá um erro de tempo de compilação. Caso contrário, a resolução de sobrecarga será aplicada com a lista de expressões do inicializador de elementos como a lista de argumentos e o inicializador de coleção invocará o método resultante. Portanto, o objeto de coleção deve conter uma instância ou método de extensão aplicável com o nome Add
para cada inicializador de elemento.
Exemplo: o seguinte mostra uma classe que representa um contato com um nome e uma lista de números de telefone e a criação e inicialização de um
List<Contact>
:public class Contact { public string Name { get; set; } public List<string> PhoneNumbers { get; } = new List<string>(); } class A { static void M() { var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; } }
que tem o mesmo efeito que
var __clist = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); __clist.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); __clist.Add(__c2); var contacts = __clist;
em que
__clist
,__c1
e__c2
são variáveis temporárias que são invisíveis e inacessíveis.fim do exemplo
12.8.17.3 Expressões de criação de objeto anônimo
Um anonymous_object_creation_expression é usado para criar um objeto de tipo anônimo.
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
;
anonymous_object_initializer
: '{' member_declarator_list? '}'
| '{' member_declarator_list ',' '}'
;
member_declarator_list
: member_declarator (',' member_declarator)*
;
member_declarator
: simple_name
| member_access
| null_conditional_projection_initializer
| base_access
| identifier '=' expression
;
Um inicializador de objeto anônimo declara um tipo anônimo e retorna uma instância desse tipo. Um tipo anônimo é um tipo de classe sem nome que herda diretamente de object
. Os membros de um tipo anônimo constituem uma sequência de propriedades somente leitura inferidas do inicializador de objeto anônimo usado para criar uma instância do tipo. Especificamente, um inicializador de objeto anônimo do formato
new {
p₁=
e₁,
p₂=
e₂,
…
Pv=
Ev}
declara um tipo anônimo do formato
class __Anonymous1
{
private readonly «T1» «f1»;
private readonly «T2» «f2»;
...
private readonly «Tn» «fn»;
public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
{
«f1» = «a1»;
«f2» = «a2»;
...
«fn» = «an»;
}
public «T1» «p1» { get { return «f1»; } }
public «T2» «p2» { get { return «f2»; } }
...
public «Tn» «pn» { get { return «fn»; } }
public override bool Equals(object __o) { ... }
public override int GetHashCode() { ... }
}
em que cada «Tx» é o tipo da expressão correspondente «ex». A expressão usada em um member_declarator deve ter um tipo. Portanto, é um erro de tempo de compilação para uma expressão em um member_declarator ser null
ou uma função anônima.
Os nomes de um tipo anônimo e do parâmetro para seu próprio método Equals
são gerados automaticamente pelo compilador e não podem ser referenciados no texto do programa.
No mesmo programa, dois inicializadores de objetos anônimos que especificam uma sequência de propriedades dos mesmos nomes e tipos de tempo de compilação na mesma ordem produzirão instâncias do mesmo tipo anônimo.
Exemplo: no exemplo
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;
a atribuição na última linha é permitida porque
p1
ep2
são do mesmo tipo anônimo.fim do exemplo
Os métodos Equals
e GetHashcode
em tipos anônimos substituem os métodos herdados de object
e são definidos em termos de Equals
e GetHashcode
das propriedades, de modo que duas instâncias do mesmo tipo anônimo sejam iguais se e somente se todas as suas propriedades forem iguais.
Um declarador de membro pode ser abreviado para um nome simples (§12.8.4), um acesso de membro (§12.8.7), um inicializador de projeção condicional nulo §12.8.8 ou um acesso base (§12.8.15). Isso é chamado de inicializador de projeção e é uma abreviação para a declaração e a atribuição a uma propriedade com o mesmo nome. Especificamente, declaradores de membros das formas
«identifier»
, «expr» . «identifier»
e «expr» ? . «identifier»
são exatamente equivalentes ao seguinte, respectivamente:
«identifer» = «identifier»
, «identifier» = «expr» . «identifier»
e «identifier» = «expr» ? . «identifier»
Assim, em um inicializador de projeção, o identificador seleciona o valor e o campo ou a propriedade ao quais o valor é atribuído. Intuitivamente, um inicializador de projeção projeta não apenas um valor, mas também o nome do valor.
12.8.17.4 Expressões de criação de matriz
array_creation_expression é usado para criar uma nova instância de array_type.
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Uma expressão de criação de matriz do primeiro formato aloca uma instância de matriz do tipo que resulta da exclusão de cada uma das expressões individuais da lista de expressões.
Exemplo: a expressão de criação da matriz
new int[10,20]
produz uma instância de matriz do tipoint[,]
e a nova expressão de criação da matrizint[10][,]
produz uma instância de matriz do tipoint[][,]
. fim do exemplo
Cada expressão na lista de expressões deve ser do tipo int
, uint
, long
ou ulong
ou implicitamente conversível para um ou mais desses tipos. O valor de cada expressão determina o tamanho da dimensão correspondente na instância de matriz recém-alocada. Como o comprimento de uma dimensão de matriz deve ser não negativo, é um erro em tempo de compilação ter uma expressão constante com um valor negativo na lista de expressões.
Exceto em um contexto não seguro (§23.2), o layout das matrizes não é especificado.
Se uma expressão de criação de matriz do primeiro formato incluir um inicializador de matriz, cada expressão na lista de expressões será uma constante e os tamanhos de dimensão e a classificação especificados pela lista de expressões corresponderão aos do inicializador da matriz.
Em uma expressão de criação de matriz do segundo ou terceiro formato, a classificação do tipo de matriz especificada ou do especificador de classificação deve corresponder à do inicializador de matriz. Os tamanhos de dimensão individuais são inferidos do número de elementos em cada um dos níveis de aninhamento correspondentes do inicializador de matriz. Assim, a expressão de inicialização na declaração a seguir
var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};
corresponde exatamente a
var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};
Uma expressão de criação de matriz do terceiro formato é conhecida como uma expressão de criação de matriz tipada implicitamente. É semelhante ao segundo formato, exceto que o tipo de elemento da matriz não é fornecido explicitamente, mas determinado como o melhor tipo comum (§12.6.3.15) do conjunto de expressões no inicializador de matriz. Para uma matriz multidimensional, ou seja, uma em que o rank_specifier contém pelo menos uma vírgula, esse conjunto inclui todas as expressões encontradas em array_initializers aninhados.
Inicializadores de matriz são descritos mais adiante em §17.7.
O resultado da avaliação de uma expressão de criação de matriz é classificado como um valor, ou seja, uma referência à instância de matriz recém-alocada. O processamento em run-time de uma expressão de criação de matriz consiste nas seguintes etapas:
- As expressões de tamanho da dimensão da expression_list são avaliadas em ordem, da esquerda para a direita. Após a avaliação de cada expressão, uma conversão implícita (§10.2) para um dos seguintes tipos é executada:
int
,uint
,long
,ulong
. O primeiro tipo nessa lista para o qual existe uma conversão implícita é escolhido. Se a avaliação de uma expressão ou a conversão implícita subsequente causar uma exceção, nenhuma expressão adicional será avaliada e nenhuma outra etapa será executada. - Os valores computados para os tamanhos de dimensão são validados da seguinte maneira: se um ou mais dos valores forem menores que zero,
System.OverflowException
será gerado e nenhuma etapa adicional será executada. - Uma instância de matriz com os tamanhos de dimensão especificados é alocada. Se não houver memória suficiente disponível para alocar a nova instância,
System.OutOfMemoryException
será gerado e nenhuma outra etapa será executada. - Todos os elementos da nova instância de matriz são inicializados com seus valores padrão (§9.3).
- Se a expressão de criação da matriz contiver um inicializador de matriz, cada expressão no inicializador de matriz será avaliada e atribuída ao elemento de matriz correspondente. As avaliações e as atribuições são executadas na ordem em que as expressões são escritas no inicializador da matriz – em outras palavras, os elementos são inicializados em ordem de índice crescente, com a dimensão mais à direita aumentando primeiro. Se a avaliação de uma determinada expressão ou a atribuição subsequente ao elemento de matriz correspondente causar uma exceção, nenhum elemento adicional será inicializado (e os elementos restantes terão, portanto, seus valores padrão).
Uma expressão de criação de matriz permite a instanciação de uma matriz com elementos de um tipo de matriz, mas os elementos de tal matriz devem ser inicializados manualmente.
Exemplo: a instrução
int[][] a = new int[100][];
cria uma matriz unidimensional com 100 elementos do tipo
int[]
. O valor inicial de cada elemento énull
. Não é possível que a mesma expressão de criação de matriz também instancie as submatrizes e a instruçãoint[][] a = new int[100][5]; // Error
resulta em um erro de tempo de compilação. A instanciação das submatrizes pode, em vez disso, ser executada manualmente, como em
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }
fim do exemplo
Observação: quando uma matriz de matrizes tem uma forma "retangular", ou seja, quando as submatrizes têm o mesmo tamanho, é mais eficiente usar uma matriz multidimensional. No exemplo acima, a instanciação da matriz de matrizes cria 101 objetos – uma matriz externa e 100 submatrizes. Em contraste,
int[,] a = new int[100, 5];
cria apenas um único objeto, uma matriz bidimensional e realiza a alocação em uma única instrução.
fim da observação
Exemplo: veja a seguir exemplos de expressões de criação de matriz tipadas implicitamente:
var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,] var d = new[] { 1, "one", 2, "two" }; // Error
A última expressão causa um erro de tempo de compilação porque nem
int
nemstring
é implicitamente conversível para o outro e, portanto, não há o melhor tipo comum. Uma expressão de criação de matriz tipada explicitamente deve ser usada nesse caso, por exemplo, especificando-se o tipo a serobject[]
. Como alternativa, um dos elementos pode ser convertido em um tipo base comum, que se tornaria o tipo de elemento inferido.fim do exemplo
Expressões de criação de matriz digitadas implicitamente podem ser combinadas com inicializadores de objetos anônimos (§12.8.17.3) para criar estruturas de dados tipadas anonimamente.
Exemplo:
var contacts = new[] { new { Name = "Chris Smith", PhoneNumbers = new[] { "206-555-0101", "425-882-8080" } }, new { Name = "Bob Harris", PhoneNumbers = new[] { "650-555-0199" } } };
fim do exemplo
12.8.17.5 Expressões de criação de delegado
Uma delegate_creation_expression é usada para obter uma instância de um delegate_type.
delegate_creation_expression
: 'new' delegate_type '(' expression ')'
;
O argumento de uma expressão de criação de delegado deve ser um grupo de métodos, uma função anônima ou um valor do tipo de tempo de compilação dynamic
ou um delegate_type. Se o argumento for um grupo de métodos, ele identificará o método e, para um método de instância, o objeto para o qual criar um delegado. Se o argumento for uma função anônima, ele definirá diretamente os parâmetros e o corpo do método do destino delegado. Se o argumento for um valor, ele identificará uma instância delegada da qual criar uma cópia.
Se a expressão tiver o tipo de tempo de compilação dynamic
, a delegate_creation_expression será associada dinamicamente (§12.8.17.5) e as regras abaixo serão aplicadas durante a execução usando o tipo de tempo de execução da expressão. Caso contrário, as regras serão aplicadas em tempo de compilação.
O processamento de tempo de associação de uma delegate_creation_expression do novo formato D(E)
, onde D
é um delegate_type e E
é uma expressão, consiste nas seguintes etapas:
Se
E
for um grupo de métodos, a expressão de criação de delegado será processada da mesma forma que uma conversão de grupo de métodos (§10.8) deE
paraD
.Se
E
for uma função anônima, a expressão de criação de delegado será processada da mesma forma que uma conversão de função anônima (§10.7) deE
paraD
.Se
E
for um valor,E
será compatível (§20.2) comD
, e o resultado será uma referência a um delegado recém-criado com uma lista de invocação de entrada única que invocaE
.
O processamento em run-time de uma delegate_creation_expression do novo formato D(E)
, onde D
é um delegate_type e E
é uma expressão, consiste nas seguintes etapas:
- Se
E
for um grupo de métodos, a expressão de criação de delegado será avaliada como uma conversão de grupo de métodos (§10.8) deE
paraD
. - Se
E
for uma função anônima, a criação de delegado será avaliada como uma conversão de função anônima deE
paraD
(§10.7). - Se
E
for um valor de delegate_type:-
E
é avaliado. Se essa avaliação causar uma exceção, nenhuma etapa adicional será executada. - Se o valor de
E
fornull
,System.NullReferenceException
será lançado e nenhuma etapa adicional será executada. - Uma nova instância do tipo de delegado
D
é alocada. Se não houver memória suficiente disponível para alocar a nova instância,System.OutOfMemoryException
será gerado e nenhuma outra etapa será executada. - A nova instância de delegado é inicializada com uma lista de invocação de única entrada que invoca
E
.
-
A lista de invocação de um delegado é determinada quando o delegado é instanciado e, em seguida, permanece constante durante todo o tempo de vida de delegado. Em outras palavras, não é possível alterar as entidades chamáveis de destino de um delegado depois que ele for criado.
Observação: lembre-se de que, quando dois delegados são combinados ou um é removido de outro, um novo delegado resulta; nenhum delegado existente tem seu conteúdo alterado. fim da observação
Não é possível criar um delegado que se refira a uma propriedade, indexador, operador definido pelo usuário, construtor de instância, finalizador ou construtor estático.
Exemplo: conforme descrito acima, quando um delegado é criado direto de um grupo de métodos, a lista de parâmetros e o tipo de retorno de delegado determinam quais dos métodos sobrecarregados selecionar. No exemplo
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) => x * x; static double Square(double x) => x * x; }
o campo
A.f
é inicializado com um delegado que se refere ao segundo métodoSquare
porque esse método corresponde exatamente à lista de parâmetros e ao tipo de retorno deDoubleFunc
. Se o segundo métodoSquare
não estivesse presente, um erro de tempo de compilação teria ocorrido.fim do exemplo
12.8.18 Operador typeof
O operador typeof
é usado para obter o objeto System.Type
para um tipo.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier? ('.' identifier generic_dimension_specifier?)*
| unbound_qualified_alias_member ('.' identifier generic_dimension_specifier?)*
;
unbound_qualified_alias_member
: identifier '::' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
O primeiro formato de typeof_expression consiste em uma palavra-chave typeof
seguida por um tipo entre parênteses. O resultado de uma expressão desse formato é o objeto System.Type
para o tipo indicado. Há apenas um objeto System.Type
para qualquer tipo específico. Isso significa que, para um tipo T
, typeof(T) == typeof(T)
é sempre verdadeiro. O tipo não pode ser dynamic
.
O segundo formato de typeof_expression consiste em uma palavra-chave typeof
seguida por um unbound_type_nameentre parênteses.
Observação: as gramáticas de unbound_type_name e unbound_qualified_alias_member seguem as de type_name (§7.8) e qualified_alias_member (§14.8.1), exceto que os generic_dimension_specifiersão substituídos por type_argument_lists. fim da observação
Ao reconhecer o operando de uma typeof_expression se unbound_type_name e type_name forem aplicáveis, ou seja, quando não contiver um generic_dimension_specifier nem um type_argument_list, type_name será escolhido.
Observação: o ANTLR faz a escolha especificada automaticamente devido à ordenação das alternativas de typeof_expression. fim da observação
O significado de um unbound_type_name é determinado como se:
- A sequência de tokens é convertida em um type_name substituindo cada generic_dimension_specifier por um type_argument_list com o mesmo número de vírgulas e a palavra-chave
object
que cada type_argument. - O type_name resultante é resolvido para um tipo construído (§7,8).
- O unbound_type_name é então o tipo genérico não associado associado ao tipo construído resolvido (§8.4).
Observação: não há nenhum requisito para que uma implementação transforme a sequência de tokens ou produza o tipo construído intermediário, apenas que o tipo genérico não associado que é determinado é "como se" esse processo fosse seguido. fim da observação
É um erro que o nome do tipo seja um tipo de referência anulável.
O resultado de typeof_expression é o objeto System.Type
para o tipo genérico não associado resultante.
A terceira forma de typeof_expression consiste em uma palavra-chave typeof
seguida por uma palavra-chave void
entre parênteses. O resultado de uma expressão desse formato é o objeto System.Type
que representa a ausência de um tipo. O objeto type retornado por typeof(void)
é distinto do objeto type retornado para qualquer tipo.
Observação: este objeto especial
System.Type
é útil em bibliotecas de classes que permitem reflexão de métodos da linguagem, em que esses métodos desejam ter uma maneira de representar o tipo de retorno de qualquer método, incluindo os métodosvoid
, com uma instância deSystem.Type
. fim da observação
O operador typeof
pode ser usado em um parâmetro de tipo. É um erro de tempo de compilação se o nome do tipo for conhecido por ser um tipo de referência anulável. O resultado é o objeto System.Type
para o tipo de run-time associado ao parâmetro de tipo. Se o tipo de run-time for um tipo de referência anulável, o resultado será o tipo de referência não anulável correspondente. O operador typeof
também pode ser usado em um tipo construído ou em um tipo genérico não associado (§8.4.4). O objeto System.Type
para um tipo genérico não associado não é o mesmo que o objeto System.Type
do tipo de instância (§15.3.2). O tipo de instância é sempre um tipo construído fechado em run-time, de modo que seu objeto System.Type
depende dos argumentos de tipo de run-time em uso. O tipo genérico não associado, por outro lado, não tem argumentos de tipo e produz o mesmo objeto System.Type
, independentemente dos argumentos de tipo de run-time.
Exemplo: o exemplo
class X<T> { public static void PrintTypes() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void), typeof(T), typeof(X<T>), typeof(X<X<T>>), typeof(X<>) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i]); } } } class Test { static void Main() { X<int>.PrintTypes(); } }
produz a saída a seguir:
System.Int32 System.Int32 System.String System.Double[] System.Void System.Int32 X`1[System.Int32] X`1[X`1[System.Int32]] X`1[T]
Observe que
int
eSystem.Int32
são do mesmo tipo. O resultado detypeof(X<>)
não depende do argumento de tipo, mas o resultado detypeof(X<T>)
depende.fim do exemplo
12.8.19 Operador sizeof
O operador sizeof
retorna o número de bytes de 8 bits ocupados por uma variável de um determinado tipo. O tipo especificado como um operando ao sizeof deve ser um unmanaged_type (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
Para determinados tipos predefinidos, o operador sizeof
produz um valor de int
constante, conforme mostrado na tabela abaixo:
Expressão | Resultado |
---|---|
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
oito |
sizeof(ulong) |
oito |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
oito |
sizeof(bool) |
1 |
sizeof(decimal) |
16 |
Para um tipo de enumeração T
, o resultado da expressão sizeof(T)
é um valor constante igual ao tamanho de seu tipo subjacente, conforme fornecido acima. Para todos os outros tipos de operando, o operador sizeof
é especificado em §23.6.9.
12.8.20 Os operadores verificados e não verificados
Os operadores checked
e unchecked
são usados para controlar o contexto de verificação de estouro para operações e conversões aritméticas do tipo integral.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
O operador checked
avalia a expressão contida em um contexto verificado e o operador unchecked
avalia a expressão contida em um contexto não verificado. Uma checked_expression ou unchecked_expression corresponde exatamente a uma parenthesized_expression (§12.8.5), com exceção de que a expressão contida é avaliada no contexto de verificação de estouro fornecido.
O contexto de verificação de estouro também pode ser controlado com as instruções checked
e unchecked
(§13.12).
As seguintes operações são afetadas pelo contexto de verificação de estouro estabelecido pelos operadores e instruções verificados e não verificados:
- Os operadores predefinidos
++
e--
(§12.8.16 e §12.9.6), quando o operando é de um tipo integral ou de enumeração. - O operador predefinido
-
unário (§12.9.3), quando o operando é de um tipo integral. - Os operadores binários predefinidos
+
,-
,*
e/
(§12.10), quando os dois operandos são de tipos integrais ou enumerados. - Conversões numéricas explícitas (§10.3.2) de um tipo integral ou enum para outro tipo integral ou enum, ou de
float
oudouble
para um tipo integral ou enum.
Quando uma das operações acima produz um resultado muito grande para se representar no tipo de destino, o contexto no qual a operação é executada controla o comportamento resultante:
- Em um contexto
checked
, se a operação for uma expressão constante (§12.23), ocorrerá um erro em tempo de compilação. Caso contrário, quando a operação for executada em run-time,System.OverflowException
será lançado. - Em um contexto
unchecked
, o resultado é truncado pelo descarte dos bits de ordem superior que não se ajustam ao tipo de destino.
Para expressões não constantes (§12.23) (expressões que são avaliadas em tempo de execução) que não estão delimitadas por nenhum operador ou instrução checked
ou unchecked
, o contexto padrão de verificação de estouro é não verificado, a menos que fatores externos (como opções do compilador e configuração do ambiente de execução) requeiram avaliação verificada.
Para expressões de constante (§12.23) (expressões que podem ser totalmente avaliadas no tempo de compilação), o contexto de verificação de estouro padrão sempre é verificado. A menos que uma expressão de constante seja explicitamente colocada em um contexto unchecked
, estouros que ocorrerem durante a avaliação do tempo de compilação da expressão sempre causarão erros de tempo de compilação.
O corpo de uma função anônima não é afetado por contextos checked
ou unchecked
em que a função anônima ocorre.
Exemplo: no código a seguir
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() => checked(x * y); // Throws OverflowException static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Depends on default }
nenhum erro de tempo de compilação é relatado, pois nenhuma das expressões pode ser avaliada em tempo de compilação. Em run-time, o método
F
lança umSystem.OverflowException
e o métodoG
retorna –727379968 (os 32 bits inferiores do resultado fora do intervalo). O comportamento do métodoH
depende do contexto de verificação de estouro padrão da compilação, mas é igual aF
ouG
.fim do exemplo
Exemplo: no código a seguir
class Test { const int x = 1000000; const int y = 1000000; static int F() => checked(x * y); // Compile-time error, overflow static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Compile-time error, overflow }
os estouros que ocorrem ao avaliar as expressões constantes em
F
eH
fazem com que erros de tempo de compilação sejam relatados porque as expressões são avaliadas em um contextochecked
. Um estouro também ocorre ao avaliar a expressão constante emG
, mas como a avaliação ocorre em um contexto deunchecked
, o estouro não é relatado.fim do exemplo
Os operadores checked
e unchecked
afetam apenas o contexto de verificação de estouro das operações contidas textualmente nos tokens "(
" e ")
". Os operadores não têm efeito sobre os membros da função que são invocados como resultado da avaliação da expressão contida.
Exemplo: no código a seguir
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }
o uso de
checked
em F não afeta a avaliação dex * y
emMultiply
, assim,x * y
é avaliado no contexto padrão de verificação de estouro.fim do exemplo
O operador unchecked
é conveniente ao escrever constantes dos tipos integrais assinados na notação hexadecimal.
Exemplo:
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }
As duas constantes hexadecimal acima são do tipo
uint
. Como as constantes estão fora do intervalo deint
, sem o operadorunchecked
, as conversões paraint
produziriam erros de tempo de compilação.fim do exemplo
Observação: os operadores e as instruções
checked
eunchecked
permitem que os programadores controlem alguns aspectos de alguns cálculos numéricos. No entanto, o comportamento de alguns operadores numéricos depende dos tipos de dados de seus operandos. Por exemplo, multiplicar dois números decimais sempre resulta em uma exceção de estouro mesmo dentro de um bloco explicitamente não verificado. Da mesma forma, multiplicar dois floats nunca resulta em uma exceção por estouro, mesmo dentro de uma construção explicitamente verificada. Além disso, outros operadores nunca são afetados pelo modo de verificação, seja ele padrão ou explícito. fim da observação
12.8.21 Expressões de valor padrão
Uma expressão de valor padrão é usada para obter o valor padro (§9.3) de um tipo.
default_value_expression
: explictly_typed_default
| default_literal
;
explictly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
default_literal representa um valor padrão (§9.3). Ele não tem um tipo, mas pode ser convertido em qualquer tipo por meio de uma conversão literal padrão (§10.2.16).
O resultado de uma default_value_expression é o padrão (§9.3) do tipo explícito em um explictly_typed_defaultou o tipo de destino da conversão para uma default_value_expression.
default_value_expression é uma expressão constante (§12.23) se o tipo for um dos seguintes:
- um tipo de referência
- um parâmetro de tipo conhecido por ser um tipo de referência (§8.2);
- um dos seguintes tipos de valor:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
,bool,
; ou - qualquer tipo de enumeração.
12.8.22 Alocação da pilha
Uma expressão de alocação de pilha aloca um bloco de memória na pilha de execução. A pilha de execução é uma área de memória em que as variáveis locais são armazenadas. A pilha de execução não faz parte do heap gerenciado. A memória usada para o armazenamento de variável local é recuperada automaticamente quando a função atual retorna.
As regras de contexto seguro para uma expressão de alocação de pilha são descritas em §16.4.15.7.
stackalloc_expression
: 'stackalloc' unmanaged_type '[' expression ']'
| 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
;
stackalloc_initializer
: '{' stackalloc_initializer_element_list '}'
;
stackalloc_initializer_element_list
: stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
;
stackalloc_element_initializer
: expression
;
unmanaged_type (§8.8) indica o tipo dos itens que serão armazenados no local recém-alocado e a expressão indica o número desses itens. Juntos, eles especificam o tamanho de alocação necessário. O tipo de expressão deve ser conversível implicitamente para o tipo int
.
Como o tamanho de uma alocação de pilha não pode ser negativo, é um erro de tempo de compilação especificar o número de itens como uma constant_expression que resulta em um valor negativo.
Em run-time, se o número de itens a serem alocados for um valor negativo, o comportamento será indefinido. Se for zero, nenhuma alocação será feita e o valor retornado será definido pela implementação. Se não houver memória suficiente disponível para alocar os itens, System.StackOverflowException
será gerado.
Quando stackalloc_initializer está presente:
- Se unmanaged_type for omitido, será inferido seguindo as regras de melhor tipo comum (§12.6.3.15) para o conjunto de stackalloc_element_initializers.
- Caso constant_expression seja omitido, o número de stackalloc_element_initializers será inferido.
- Se constant_expression estiver presente, deverá ser igual ao número de stackalloc_element_initializers.
Cada stackalloc_element_initializer deve ter uma conversão implícita para unmanaged_type (§10.2). O stackalloc_element_initializerinicializa elementos na memória alocada em ordem crescente, começando com o elemento no índice zero. Na ausência de stackalloc_initializer, o conteúdo da memória recém-alocada é indefinido.
Se uma stackalloc_expression ocorrer diretamente como a expressão de inicialização de uma local_variable_declaration (§13.6.2), em que o local_variable_type é um tipo de ponteiro (§23.3) ou inferido (var
), o resultado da stackalloc_expression será um ponteiro do tipo T*
(§23.9). Nesse caso, stackalloc_expression deve aparecer em código não seguro. Caso contrário, o resultado de stackalloc_expression é uma instância do tipo Span<T>
, em que T
é unmanaged_type:
-
Span<T>
(§C.3) é um tipo de estrutura ref (§16.2.3), que apresenta um bloco de memória, aqui o bloco alocado por stackalloc_expression, como uma coleção indexável de itens tipados (T
). - A propriedade
Length
do resultado retorna o número de itens alocados. - O indexador do resultado (§15.9) retorna uma variable_reference (§9.5) para um item do bloco alocado e o intervalo é verificado.
Inicializadores de alocação de pilha não são permitidos em blocos catch
ou finally
(§13.11).
Observação: não há como liberar explicitamente a memória alocada usando-se
stackalloc
. fim da observação
Todos os blocos de memória alocados em pilha criados durante a execução de um membro da função são descartados automaticamente quando esse membro da função é retornado.
Com exceção do operador stackalloc
, C# não fornece construções predefinidas para gerenciar memória coletada não de lixo. Esses serviços normalmente são fornecidos por meio do suporte a bibliotecas de classes ou importados diretamente do sistema operacional subjacente.
Exemplo:
// Memory uninitialized Span<int> span1 = stackalloc int[3]; // Memory initialized Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred Span<int> span3 = stackalloc[] { 11, 12, 13 }; // Error; result is int*, not allowed in a safe context var span4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion from Span<int> to Span<long> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns Span<long> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns Span<long> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // Implicit conversion of Span<T> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // Implicit conversion of Span<T> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; public class Widget<T> { public static implicit operator Widget<T>(Span<double> sp) { return null; } }
No caso de
span8
,stackalloc
resulta em umSpan<int>
, que é convertido por um operador implícito emReadOnlySpan<int>
. Da mesma forma, paraspan9
,Span<double>
resultante é convertido no tipo definido pelo usuárioWidget<double>
usando-se a conversão, conforme mostrado. fim do exemplo
12.8.23 Operador nameof
nameof_expression é usado para obter o nome de uma entidade de programa como uma cadeia de caracteres constante.
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: named_entity_target ('.' identifier type_argument_list?)*
;
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
Como nameof
não é uma palavra-chave, nameof_expression é sempre sintaticamente ambíguo com uma invocação do nome simples nameof
. Por motivos de compatibilidade, se uma pesquisa (§12.8.4) do nome nameof
for bem-sucedida, a expressão será tratada como uma invocation_expression, independentemente de a invocação ser válida. Caso contrário, é uma nameof_expression.
Pesquisas de acesso a membro e nome simples são realizadas em named_entity durante o tempo de compilação, seguindo-se as regras descritas em §12.8.4 e §12.8.7. No entanto, quando a pesquisa descrita em §12.8.4 e §12.8.7 resulta em um erro porque um membro da instância foi encontrado em um contexto estático, nameof_expression não produz esse erro.
É um erro em tempo de compilação de uma named_entity que designa um grupo de métodos para ter uma type_argument_list. É um erro de tempo de compilação para um named_entity_target ter o tipo dynamic
.
nameof_expression é uma expressão constante do tipo string
e não tem efeito em run-time. Especificamente, a named_entity não é avaliada e é ignorada para fins de análise de atribuição definitiva (§9.4.4.22). Seu valor é o último identificador da named_entity antes da type_argument_list final opcional, transformado do formato seguinte:
- O prefixo "
@
", se usado, é removido. - Cada unicode_escape_sequence é transformada no caractere Unicode correspondente.
- Todos os formatting_characters são removidos.
Essas são as mesmas transformações aplicadas em §6.4.3 ao testar-se a igualdade entre identificadores.
Exemplo: veja a seguir os resultados de várias expressões
nameof
, supondo um tipo genéricoList<T>
declarado no namespaceSystem.Collections.Generic
:using TestAlias = System.String; class Program { static void Main() { var point = (x: 3, y: 4); string n1 = nameof(System); // "System" string n2 = nameof(System.Collections.Generic); // "Generic" string n3 = nameof(point); // "point" string n4 = nameof(point.x); // "x" string n5 = nameof(Program); // "Program" string n6 = nameof(System.Int32); // "Int32" string n7 = nameof(TestAlias); // "TestAlias" string n8 = nameof(List<int>); // "List" string n9 = nameof(Program.InstanceMethod); // "InstanceMethod" string n10 = nameof(Program.GenericMethod); // "GenericMethod" string n11 = nameof(Program.NestedClass); // "NestedClass" // Invalid // string x1 = nameof(List<>); // Empty type argument list // string x2 = nameof(List<T>); // T is not in scope // string x3 = nameof(GenericMethod<>); // Empty type argument list // string x4 = nameof(GenericMethod<T>); // T is not in scope // string x5 = nameof(int); // Keywords not permitted // Type arguments not permitted for method group // string x6 = nameof(GenericMethod<Program>); } void InstanceMethod() { } void GenericMethod<T>() { string n1 = nameof(List<T>); // "List" string n2 = nameof(T); // "T" } class NestedClass { } }
Partes potencialmente surpreendentes deste exemplo são a resolução de
nameof(System.Collections.Generic)
como apenas "Generic" em vez do namespace completo e denameof(TestAlias)
como "TestAlias" em vez de "String". fim do exemplo
12.8.24 Expressões de método anônimo
Uma anonymous_method_expression é uma das duas maneiras de definir uma função anônima. Isso será descrito mais abaixo em §12.19.
12.9 Operadores unários
12.9.1 Geral
Os operadores +
, -
, !
(apenas negação lógica §12.9.4), ~
, ++
, --
, conversão e await
são chamados de operadores unários.
Observação: o operador tolerante a valores nulos pós-fixado (§12.8.9),
!
, devido à sua natureza somente de tempo de compilação e não sobrecarregável, é excluído da lista acima. fim da observação
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator unary_expression
| '~' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| pointer_indirection_expression // unsafe code support
| addressof_expression // unsafe code support
;
pointer_indirection_expression (§23.6.2) e addressof_expression (§23.6.5) estão disponíveis somente em código não seguro (§23).
Se o operando de uma unary_expression tiver o tipo de tempo de compilação dynamic
, ele será associado dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da unary_expression é dynamic
, e a resolução descrita abaixo ocorrerá em run-time usando o tipo de run-time do operando.
12.9.2 Operador de adição de unário
Para uma operação do formato +x
, a resolução de sobrecarga do operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. O operando é convertido no tipo de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador. Os operadores de adição de unários predefinidos são:
int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);
Para cada um desses operadores, o resultado é o valor do operando.
As formas elevadas (§12.4.8) dos operadores unários de adição predefinidos anteriormente mencionados também são predefinidas.
12.9.3 Operador de subtração de unário
Para uma operação do formato –x
, a resolução de sobrecarga do operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. O operando é convertido no tipo de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador. Os operadores de subtração de unários predefinidos são:
Negação de inteiro:
int operator –(int x); long operator –(long x);
O resultado é computado subtraindo-se
X
de zero. Se o valor deX
for o menor valor representável do tipo do operando (−2³¹ paraint
ou −2⁶³ paralong
), então a negação matemática deX
não será representável dentro do tipo do operando. Se isso ocorrer em um contextochecked
, umaSystem.OverflowException
será lançada; se ocorrer em um contextounchecked
, o resultado será o valor do operando e o estouro não será relatado.Se o operando do operador de negação for do tipo
uint
, ele será convertido para o tipolong
, e o tipo do resultado serálong
. Uma exceção é a regra que permite o valorint
−2147483648
(-2³¹) ser escrito como um literal inteiro decimal (§6.4.5.3).Se o operando do operador de negação for do tipo
ulong
, ocorrerá um erro de tempo de compilação. Uma exceção é a regra que permite que o valorlong
−9223372036854775808
(-2⁶³) seja escrito como um literal decimal inteiro (§6.4.5.3)Negação de ponto flutuante:
float operator –(float x); double operator –(double x);
O resultado é o valor de
X
com seu sinal invertido. Sex
forNaN
, o resultado também seráNaN
.Negação decimal:
decimal operator –(decimal x);
O resultado é computado subtraindo-se
X
de zero. A negação decimal é equivalente ao uso do operador de subtração unário do tipoSystem.Decimal
.
As formas elevadas (§12.4.8) dos operadores unários de subtração predefinidos anteriormente mencionados também são predefinidas.
12.9.4 Operador de negação lógico
Para uma operação do formato !x
, a resolução de sobrecarga do operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. O operando é convertido no tipo de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador. Existe apenas um operador de negação lógico predefinido:
bool operator !(bool x);
Esse operador computa a negação lógica do operando: se o operando for true
, o resultado será false
. Se o operando for false
, o resultado será true
.
As formas elevadas (§12.4.8) do operador de negação lógica predefinido e não elevado definido acima também são predefinidas.
Observação: os operadores de negação lógica de prefixo e de perdão nulo de sufixo (§12.8.9), embora representados pelo mesmo símbolo léxico (!
), são distintos.
fim da observação
12.9.5 Operador de complemento bit a bit
Para uma operação do formato ~x
, a resolução de sobrecarga do operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. O operando é convertido no tipo de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador. Os operadores de complemento bit a bit predefinidos são:
int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);
Para cada um desses operadores, o resultado da operação é o complemento bit a bit de x
.
Cada tipo de enumeração E
fornece implicitamente o seguinte operador de complemento bit a bit:
E operator ~(E x);
O resultado da avaliação de ~x
, em que X
é uma expressão de um tipo de enumeração E
com um tipo subjacente U
, é exatamente o mesmo que avaliar (E)(~(U)x)
, exceto que a conversão em E
sempre é executada como se estivesse em um contexto de unchecked
(§12.8.20).
As formas elevadas (§12.4.8) dos operadores de complemento bit a bit predefinidos não elevados anteriormente mencionados também são predefinidas.
12.9.6 Operadores de incremento e de decremento pré-fixados
pre_increment_expression
: '++' unary_expression
;
pre_decrement_expression
: '--' unary_expression
;
O operando de uma operação de adição ou remoção pré-fixada deve ser uma expressão classificada como uma variável, um acesso de propriedade ou um acesso de indexador. O resultado da operação é um valor do mesmo tipo do operando.
Se o operando de uma operação de adição ou remoção prefixada for um acesso de propriedade ou indexador, a propriedade ou o indexador deverá ter acessores get e set. Se esse não for o caso, ocorrerá um erro de tempo de associação.
A resolução de sobrecarga de operador unário (§12.4.4) é aplicada para selecionar uma implementação de operador específica. Há operadores predefinidos ++
e --
para os seguintes tipos: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
e qualquer tipo de enumeração. Os operadores predefinidos ++
retornam o valor produzido adicionando-se 1
ao operando e os operadores predefinidos --
retornam o valor produzido pela subtração de 1
do operando. Em um contexto checked
, se o resultado dessa adição ou subtração estiver fora do intervalo do tipo de resultado e o tipo de resultado for um tipo integral ou tipo de enumeração, System.OverflowException
será lançado.
Deve haver uma conversão implícita do tipo de retorno do operador unário selecionado para o tipo unary_expression; caso contrário, ocorrerá um erro de tempo de compilação.
O processamento run-time de uma operação de incremento ou decremento pré-fixada no formato ++x
ou --x
consiste nas seguintes etapas:
- Se
x
for classificado como uma variável:-
x
é avaliado para produzir a variável. - O valor de
x
é convertido no tipo de operando do operador selecionado e o operador é invocado com esse valor como seu argumento. - O valor retornado pelo operador é convertido no tipo de
x
. O valor resultante é armazenado no local fornecido pela avaliação dex
e torna-se o resultado da operação.
-
- Se
x
for classificado como uma propriedade ou acesso de indexador:- A expressão de instância (se
x
não forstatic
) e a lista de argumentos (sex
for um acesso de indexador) associadas ax
serão avaliadas e os resultados serão usados nas invocações subsequentes do acessador get e set. - O acessador get de
x
é invocado. - O valor retornado pelo acessador get é convertido no tipo de operando do operador selecionado e o operador é invocado com esse valor como seu argumento.
- O valor retornado pelo operador é convertido no tipo de
x
. O acessador set dex
é invocado com esse valor como seu argumento de valor. - Esse valor também se torna o resultado da operação.
- A expressão de instância (se
Os operadores ++
e --
também dão suporte à notação de pós-fixada (§12.8.16). O resultado de x++
ou x--
é o valor de x
antes da operação, enquanto o resultado de ++x
ou --x
for o valor de x
depois da operação. Nos dois casos, x
em si tem o mesmo valor após a operação.
Uma implementação do operador ++
ou do operador --
pode ser invocada usando a notação pós-fixada ou pré-fixada. Não é possível ter implementações de operador separadas para as duas notações.
As formas elevadas (§12.4.8) dos operadores de adição e remoção pós-fixados predefinidos não elevados definidos acima também são predefinidos.
12.9.7 Expressões de conversão
cast_expression é usado para converter explicitamente uma expressão em um determinado tipo.
cast_expression
: '(' type ')' unary_expression
;
Uma cast_expression do formato (T)E
, em que T
é um tipo e E
é uma unary_expression, executa uma conversão explícita (§10.3) do valor de E
no tipo T
. Se não houver nenhuma conversão explícita de E
para T
, ocorrerá um erro de tempo de associação. Caso contrário, o resultado será o valor produzido pela conversão explícita. O resultado é sempre classificado como um valor, mesmo que E
denote uma variável.
A gramática de cast_expression leva a algumas ambiguidades sintáticas.
Exemplo: a expressão
(x)–y
pode ser interpretada como um cast_expression (uma conversão de–y
para o tipox
) ou como uma additive_expression combinada com uma parenthesized_expression (que calcula o valorx – y
). fim do exemplo
Para resolver as ambiguidades de cast_expression, a seguinte regra existe: uma sequência de um ou mais tokens (§6.4) entre parênteses é considerada o início de cast_expression somente se pelo menos um dos seguintes itens for verdadeiro:
- A sequência de tokens é a gramática correta para um tipo, mas não para uma expressão.
- A sequência de tokens está correta para a gramática de um tipo, e o token imediatamente após o fechamento dos parênteses é o token "
~
", o token "!
", o token "(
", um identificador (§6.4.3), um literal (§6.4.5) ou qualquer palavra-chave (§6.4.4), excetoas
eis
.
O termo "gramática correta" acima significa apenas que a sequência de tokens deve estar em conformidade com a produção gramatical específica. Ele especificamente não considera o significado real de nenhum identificador constituinte.
Exemplo: se
x
ey
forem identificadores,x.y
será a gramática correta para um tipo, mesmo quex.y
não denote realmente um tipo. fim do exemplo
Observação: na regra de desambiguação, segue-se que, se
x
ey
forem identificadores,(x)y
,(x)(y)
e(x)(-y)
serão cast_expressions, mas(x)-y
não será, mesmo sex
identificar um tipo. No entanto, sex
for uma palavra-chave que identifique um tipo predefinido (comoint
), todas as quatro formas serão cast_expressions (porque essa palavra-chave não poderia ser uma expressão por si só). fim da observação
12.9.8 Expressões await
12.9.8.1 Geral
O operador await
é usado para suspender a avaliação da função assíncrona circundante até que a operação assíncrona representada pelo operando seja concluída.
await_expression
: 'await' unary_expression
;
Um await_expression só é permitido no corpo de uma função assíncrona (§15.14). Na função assíncrona circundante mais próxima, uma await_expression não deve ocorrer nestes locais:
- Em uma função anônima aninhada (não assíncrona)
- No bloco de uma lock_statement
- Em uma conversão de função anônima em um tipo de árvore de expressão (§10.7.3)
- Em um contexto não seguro
Observação: uma await_expression não pode ocorrer na maioria dos lugares em uma query_expression, pois esses lugares são transformados sintaticamente para usar expressões lambda não assíncronas. fim da observação
Em uma função assíncrona, await
não deve ser usado como um available_identifier, embora o identificador de texto @await
possa ser usado. Portanto, não há ambiguidade sintática entre await_expressione várias expressões envolvendo identificadores. Fora das funções assíncronas, await
atua como um identificador normal.
O operando de uma await_expression é chamado de tarefa. Representa uma operação assíncrona que pode ou não ser concluída no momento em que o await_expression é avaliado. A finalidade do operador await
é suspender a execução da função assíncrona delimitadora até que a tarefa aguardada seja concluída e, em seguida, obter o resultado.
12.9.8.2 Expressões aguardáveis
A tarefa de uma await_expression deve ser aguardável. Uma expressão t
será aguardável se ocorrer uma das seguintes condições:
-
t
é do tipo de tempo de compilaçãodynamic
-
t
tem uma instância acessível ou um método de extensão chamadoGetAwaiter
sem parâmetros e sem parâmetros de tipo, e um tipo de retornoA
para o qual todas as seguintes características são verdadeiras:-
A
implementa a interfaceSystem.Runtime.CompilerServices.INotifyCompletion
(daqui em diante conhecido comoINotifyCompletion
para simplificação) -
A
tem uma propriedade de instânciaIsCompleted
do tipobool
que é acessível e legível -
A
possui um método de instância acessívelGetResult
que não possui parâmetros nem parâmetros de tipo
-
O objetivo do método GetAwaiter
é obter um awaiter para a tarefa. O tipo A
é chamado de tipo awaiter para a expressão await.
A finalidade da propriedade IsCompleted
é determinar se a tarefa já foi concluída. Nesse caso, não é necessário suspender a avaliação.
A finalidade do método INotifyCompletion.OnCompleted
é associar uma "continuação" à tarefa; ou seja, um delegado (do tipo System.Action
) que será invocado assim que a tarefa for concluída.
A finalidade do método GetResult
é obter o resultado da tarefa depois que ela for concluída. Esse resultado pode ser uma conclusão bem-sucedida, possivelmente com um valor de resultado ou pode ser uma exceção gerada pelo método GetResult
.
12.9.8.3 Classificação de expressões await
A expressão await t
é classificada da mesma forma que a expressão (t).GetAwaiter().GetResult()
. Portanto, se o tipo de retorno de GetResult
for void
, a await_expression será classificada como nenhum. Se ela tiver um não void
tipo de retorno T
, await_expression será classificado como um valor do tipo T
.
12.9.8.4 Avaliação em run-time de expressões await
Em run-time, a expressão await t
é avaliada da seguinte maneira:
- Um awaiter
a
é obtido ao avaliar a expressão(t).GetAwaiter()
. -
bool
b
é obtido avaliando-se a expressão(a).IsCompleted
. - Se
b
forfalse
, a avaliação dependerá de sea
implementa a interfaceSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(posteriormente conhecida comoICriticalNotifyCompletion
para fins de brevidade). Essa verificação é feita no momento da associação; ou seja, em tempo de execução sea
tiver o tipo de tempo de compilaçãodynamic
, e caso contrário, em tempo de compilação. Deixer
indicar o delegado de retomada (§15.14):- Se
a
não implementarICriticalNotifyCompletion
, então a expressão((a) as INotifyCompletion).OnCompleted(r)
será avaliada. - Se
a
implementarICriticalNotifyCompletion
, então a expressão((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
será avaliada. - A avaliação é suspensa e o controle é retornado ao chamador atual da função assíncrona.
- Se
- Imediatamente após (se
b
fortrue
) ou após a invocação posterior de delegado de retomada (seb
foifalse
), a expressão(a).GetResult()
será avaliada. Se ela retornar um valor, esse valor será o resultado de await_expression. Caso contrário, o resultado será nada.
A implementação de um awaiter dos métodos de interface INotifyCompletion.OnCompleted
e ICriticalNotifyCompletion.UnsafeOnCompleted
deve fazer com que o delegado r
seja invocado no máximo uma vez. Caso contrário, o comportamento da função assíncrona delimitante é indefinido.
12.10 Operadores aritméticos
12.10.1 Geral
Os operadores *
, /
, %
, +
e -
são chamados de operadores aritméticos.
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression
| multiplicative_expression '/' unary_expression
| multiplicative_expression '%' unary_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
Se um operando de um operador aritmético tiver o tipo de tempo de compilação dynamic
, a expressão será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão é dynamic
e a resolução descrita abaixo ocorrerá em run-time usando-se o tipo de run-time desses operandos que têm o tipo de tempo de compilação dynamic
.
12.10.2 Operador de multiplicação
Para uma operação do formato x * y
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores de multiplicação predefinidos estão listados abaixo. Todos os operadores computam o produto de x
e y
.
Multiplicação de inteiros
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y);
Em um contexto
checked
, se o produto estiver fora do intervalo do tipo de resultado, umSystem.OverflowException
será lançado. Em um contextounchecked
, estouros não são relatados e os bits significativos de alta ordem fora do intervalo do tipo de resultado são descartados.Multiplicação de ponto flutuante:
float operator *(float x, float y); double operator *(double x, double y);
O produto é computado de acordo com as regras da aritmética IEC 60559. A tabela a seguir lista os resultados de todas as combinações possíveis de valores finitos não zeros, zeros, infinitos e NaNs. Na tabela,
x
ey
são valores positivos finitos.z
é o resultado dex * y
, arredondado para o valor representável mais próximo. Se a magnitude do resultado for muito grande para o tipo de destino,z
será infinito. Por causa do arredondamento,z
pode ser zero, embora nemx
nemy
seja zero.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+0
-0
+∞
-∞
NaN
-x
-z
+z
-0
+0
-∞
+∞
NaN
+0
+0
-0
+0
-0
NaN
NaN
NaN
-0
-0
+0
-0
+0
NaN
NaN
NaN
+∞
+∞
-∞
NaN
NaN
+∞
-∞
NaN
-∞
-∞
+∞
NaN
NaN
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Exceto quando observado de outra forma, nas tabelas de ponto flutuante em §12.10.2–§12.10.6 o uso de "
+
" significa que o valor é positivo; o uso de "-
" significa que o valor é negativo; e a falta de um sinal significa que o valor pode ser positivo ou negativo ou não tem nenhum sinal (NaN).)Multiplicação decimal:
decimal operator *(decimal x, decimal y);
Se a magnitude do valor resultante for muito grande para se representar no formato decimal,
System.OverflowException
será gerado. Por causa do arredondamento, o resultado pode ser zero, mesmo quando nenhum dos operandos é zero. A escala do resultado, antes de qualquer arredondamento, é a soma das escalas dos dois operandos. A multiplicação decimal é equivalente ao uso do operador de multiplicação do tipoSystem.Decimal
.
As formas elevadas (§12.4.8) dos operadores de multiplicação predefinidos não elevados anteriormente mencionados também são predefinidas.
12.10.3 Operador de divisão
Para uma operação do formato x / y
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores de divisão predefinidos estão listados abaixo. Todos os operadores computam o quociente de x
e y
.
Divisão de inteiros:
int operator /(int x, int y); uint operator /(uint x, uint y); long operator /(long x, long y); ulong operator /(ulong x, ulong y);
Se o valor do operando direito for zero, um
System.DivideByZeroException
será lançado.A divisão arredonda o resultado para zero. Portanto, o valor absoluto do resultado é o maior inteiro possível menor que seja menor ou igual ao valor absoluto do quociente dos dois operandos. O resultado é zero ou positivo quando os dois operandos têm o mesmo sinal e zero ou negativo quando os dois operandos têm sinais opostos.
Se o operando à esquerda for o menor valor representável de
int
oulong
e o operando à direita for–1
, ocorrerá uma situação de estouro. Em um contextochecked
, isso faz com que umaSystem.ArithmeticException
(ou uma subclasse dela) seja lançada. Em um contextounchecked
, é definido pela implementação se umaSystem.ArithmeticException
(ou uma subclasse) for lançada ou o estouro não for informado, e o valor resultante é o do operando esquerdo.Divisão de ponto flutuante:
float operator /(float x, float y); double operator /(double x, double y);
O quociente é computado de acordo com as regras da aritmética IEC 60559. A tabela a seguir lista os resultados de todas as combinações possíveis de valores finitos não zeros, zeros, infinitos e NaNs. Na tabela,
x
ey
são valores positivos finitos.z
é o resultado dex / y
, arredondado para o valor representável mais próximo.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+∞
-∞
+0
-0
NaN
-x
-z
+z
-∞
+∞
-0
+0
NaN
+0
+0
-0
NaN
NaN
+0
-0
NaN
-0
-0
+0
NaN
NaN
-0
+0
NaN
+∞
+∞
-∞
+∞
-∞
NaN
NaN
NaN
-∞
-∞
+∞
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Divisão decimal:
decimal operator /(decimal x, decimal y);
Se o valor do operando direito for zero, um
System.DivideByZeroException
será lançado. Se a magnitude do valor resultante for muito grande para se representar no formato decimal,System.OverflowException
será gerado. Por causa do arredondamento, o resultado pode ser zero, mesmo que o primeiro operando não seja zero. A escala do resultado, antes de qualquer arredondamento, é a escala mais próxima da escala preferencial que preservará um resultado igual ao resultado exato. A escala preferencial é a escala dex
menos a escala dey
.A divisão decimal é equivalente ao uso do operador de divisão do tipo
System.Decimal
.
As formas elevadas (§12.4.8) dos operadores de divisão predefinidos não elevados anteriormente mencionados também são predefinidas.
12.10.4 Operador de resto
Para uma operação do formato x % y
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores restantes predefinidos estão listados abaixo. Todos os operadores computam o restante da divisão entre x
e y
.
Resto inteiro:
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);
O resultado de
x % y
é o valor produzido porx – (x / y) * y
. Sey
for zero, umaSystem.DivideByZeroException
será lançada.Se o operando esquerdo for o menor valor de
int
oulong
e o operando direito for–1
, umaSystem.OverflowException
será lançada se e somente sex / y
lançar uma exceção.Resto de ponto flutuante:
float operator %(float x, float y); double operator %(double x, double y);
A tabela a seguir lista os resultados de todas as combinações possíveis de valores finitos não zeros, zeros, infinitos e NaNs. Na tabela,
x
ey
são valores positivos finitos.z
é o resultado dex % y
e é computado comox – n * y
, em que n é o maior inteiro possível menor ou igual ax / y
. Esse método de cálculo do restante é análogo ao usado para operandos inteiros, mas difere da definição IEC 60559 (na qualn
é o inteiro mais próximo dex / y
).+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
+z
NaN
NaN
+x
+x
NaN
-x
-z
-z
NaN
NaN
-x
-x
NaN
+0
+0
+0
NaN
NaN
+0
+0
NaN
-0
-0
-0
NaN
NaN
-0
-0
NaN
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Restante decimal:
decimal operator %(decimal x, decimal y);
Se o valor do operando direito for zero, um
System.DivideByZeroException
será lançado. É definido pela implementação quando umaSystem.ArithmeticException
(ou uma subclasse dela) é lançada. Uma implementação em conformidade não deve gerar uma exceção parax % y
em qualquer caso em quex / y
não gere uma exceção. A escala do resultado, antes de qualquer arredondamento, é a maior das escalas dos dois operandos, e o sinal do resultado, se não zero, é o mesmo que o dex
.O resto decimal é equivalente ao uso do operador de resto do tipo
System.Decimal
.Observação: essas regras garantem que, para todos os tipos, o resultado nunca tenha o sinal oposto do operando esquerdo. fim da observação
Os formatos elevados (§12.4.8) dos operadores de resto predefinidos não elevados anteriormente mencionados também são predefinidos.
12.10.5 Operador de adição
Para uma operação do formato x + y
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores de adição predefinidos estão listados abaixo. Para tipos numéricos e de enumeração, os operadores de adição predefinidos computam a soma dos dois operandos. Quando um ou os dois operandos são do tipo string
, os operadores de adição predefinidos concatenam a representação de cadeia de caracteres dos operandos.
Adição de números inteiros
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y
Em um contexto
checked
, se a soma estiver fora do intervalo do tipo de resultado,System.OverflowException
será lançado. Em um contextounchecked
, estouros não são relatados e os bits significativos de alta ordem fora do intervalo do tipo de resultado são descartados.Adição de ponto flutuante:
float operator +(float x, float y); double operator +(double x, double y);
A soma é computada de acordo com as regras da aritmética IEC 60559. A tabela a seguir lista os resultados de todas as combinações possíveis de valores finitos não zeros, zeros, infinitos e NaNs. Na tabela,
x
ey
são valores finitos diferentes de zero ez
é o resultado dex + y
. Sex
ey
tiverem a mesma magnitude, mas sinais opostos,z
será zero positivo. Sex + y
for muito grande para se representar no tipo de destino,z
será um infinito com o mesmo sinal quex + y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
+∞
-∞
NaN
+0
y
+0
+0
+∞
–∞
NaN
-0
y
+0
-0
+∞
-∞
NaN
+∞
+∞
+∞
+∞
+∞
NaN
NaN
-∞
-∞
-∞
-∞
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Adição decimal:
decimal operator +(decimal x, decimal y);
Se a magnitude do valor resultante for muito grande para se representar no formato decimal,
System.OverflowException
será gerado. A escala do resultado, antes de qualquer arredondamento, é a maior das escalas dos dois operandos.A adição decimal é equivalente ao uso do operador de adição do tipo
System.Decimal
.Adição de enumeração. Cada tipo de enumeração fornece implicitamente os seguintes operadores predefinidos, em que
E
é o tipo de enumeração eU
é o tipo subjacente deE
:E operator +(E x, U y); E operator +(U x, E y);
Em run-time, esses operadores são avaliados exatamente como
(E)((U)x + (U)y
).Concatenação de cadeia de caracteres:
string operator +(string x, string y); string operator +(string x, object y); string operator +(object x, string y);
Essas sobrecargas do operador binário
+
realizam a concatenação de strings. Se um operando de concatenação de cadeia de caracteres fornull
, uma cadeia de caracteres vazia será substituída. Caso contrário, qualquer operando nãostring
será convertido em sua representação de cadeia de caracteres através da invocação do método virtualToString
herdado do tipoobject
. SeToString
retornarnull
, uma cadeia de caracteres vazia será substituída.Exemplo:
class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // Displays s = >< int i = 1; Console.WriteLine("i = " + i); // Displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // Displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // Displays d = 2.900 } }
A saída mostrada nos comentários é o resultado típico em um sistema em inglês dos EUA. A saída precisa pode depender das configurações regionais do ambiente de execução. O próprio operador de concatenação de cadeia de caracteres se comporta da mesma maneira em cada caso, mas os métodos
ToString
implicitamente chamados durante a execução podem ser afetados pelas configurações regionais.fim do exemplo
O resultado do operador de concatenação de cadeia de caracteres é um
string
que consiste nos caracteres do operando esquerdo seguidos pelos caracteres do operando direito. O operador de concatenação de cadeia de caracteres nunca retorna um valornull
. UmaSystem.OutOfMemoryException
poderá ser lançada se não houver memória suficiente disponível para alocar a cadeia de caracteres resultante.Combinação de delegados. Cada tipo de delegado fornece implicitamente o seguinte operador predefinido, em que
D
é o tipo delegado:D operator +(D x, D y);
Se o primeiro operando for
null
, o resultado da operação será o valor do segundo operando (mesmo que também sejanull
). Caso contrário, se o segundo operando fornull
, o resultado da operação será o valor do primeiro operando. Caso contrário, o resultado da operação é uma nova instância de delegado cuja lista de invocação consiste nos elementos na lista de invocação do primeiro operando, seguido pelos elementos na lista de invocação do segundo operando. Ou seja, a lista de invocação de delegado resultante é a concatenação das listas de invocação dos dois operandos.Observação: Para exemplos de combinação de delegados, consulte §12.10.6 e §20.6. Como
System.Delegate
não é um tipo delegado, o operador + não é definido para ele. fim da observação
As formas elevadas (§12.4.8) dos operadores de adição predefinidos não elevados anteriormente mencionados também são predefinidas.
12.10.6 Operador de subtração
Para uma operação do formato x – y
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores de subtração predefinidos estão listados abaixo. Todos os operadores subtraem y
de x
.
Subtração de inteiros
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong y
Em um contexto
checked
, se a diferença estiver fora do intervalo do tipo de resultado,System.OverflowException
será lançado. Em um contextounchecked
, estouros não são relatados e os bits significativos de alta ordem fora do intervalo do tipo de resultado são descartados.Subtração de ponto flutuante:
float operator –(float x, float y); double operator –(double x, double y);
A diferença é computada de acordo com as regras da aritmética IEC 60559. A tabela a seguir lista os resultados de todas as combinações possíveis de valores finitos não zeros, zeros, infinitos e NaNs. Na tabela,
x
ey
são valores finitos diferentes de zero ez
é o resultado dex – y
. Sex
ey
forem iguais,z
será zero positivo. Sex – y
for muito grande para se representar no tipo de destino,z
será um infinito com o mesmo sinal quex – y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
-∞
+∞
NaN
+0
-y
+0
+0
-∞
+∞
NaN
-0
-y
-0
+0
-∞
+∞
NaN
+∞
+∞
+∞
+∞
NaN
+∞
NaN
-∞
-∞
-∞
-∞
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Na tabela acima, as entradas de
denotam a negação de de , não que o valor seja negativo.) Subtração decimal:
decimal operator –(decimal x, decimal y);
Se a magnitude do valor resultante for muito grande para se representar no formato decimal,
System.OverflowException
será gerado. A escala do resultado, antes de qualquer arredondamento, é a maior das escalas dos dois operandos.A subtração decimal é equivalente ao uso do operador de subtração do tipo
System.Decimal
.Subtração de enumeração. Cada tipo de enumeração fornece implicitamente o seguinte operador predefinido, em que
E
é o tipo de enumeração eU
é o tipo subjacente deE
:U operator –(E x, E y);
Esse operador é avaliado exatamente como
(U)((U)x – (U)y)
. Em outras palavras, o operador computa a diferença entre os valores ordinais dex
ey
, e o tipo do resultado é o tipo subjacente da enumeração.E operator –(E x, U y);
Esse operador é avaliado exatamente como
(E)((U)x – y)
. Em outras palavras, o operador subtrai um valor do tipo subjacente da enumeração, gerando um valor da enumeração.Remoção de delegado. Cada tipo de delegado fornece implicitamente o seguinte operador predefinido, em que
D
é o tipo delegado:D operator –(D x, D y);
A semântica é a seguinte:
- Se o primeiro operando for
null
, o resultado da operação seránull
. - Caso contrário, se o segundo operando for
null
, o resultado da operação será o valor do primeiro operando. - Caso contrário, os dois operandos representam listas de invocação não vazias (§20.2).
- Se as listas forem iguais, conforme determinado pelo operador de igualdade de delegação (§12.12.9), o resultado da operação será
null
. - Caso contrário, o resultado da operação é uma nova lista de invocação que consiste na lista do primeiro operando com as entradas do segundo operando removidas dela, desde que a lista do segundo operando seja uma sublista do primeiro. (Para determinar a igualdade da sublista, as entradas correspondentes são comparadas quanto ao operador de igualdade delegado.) Se a lista do segundo operando corresponder a várias sublistas de entradas contíguas na lista do primeiro operando, a última sublista correspondente de entradas contíguas será removida.
- Caso contrário, o resultado da operação é o valor do operando esquerdo.
- Se as listas forem iguais, conforme determinado pelo operador de igualdade de delegação (§12.12.9), o resultado da operação será
Nenhuma das listas dos operandos (se houver) é alterada no processo.
Exemplo:
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); D cd2 = new D(C.M2); D list = null; list = null - cd1; // null list = (cd1 + cd2 + cd2 + cd1) - null; // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - cd1; // M1 + M2 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2); // M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2); // M1 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1); // M1 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1); // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1); // null } }
fim do exemplo
- Se o primeiro operando for
As formas elevadas (§12.4.8) dos operadores de subtração predefinidos não elevados anteriormente mencionados também são predefinidas.
12.11 Operadores de deslocamento
Os operadores <<
e >>
são usados para executar operações de deslocamento de bits.
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
Se um operando de uma shift_expression tiver tipo de tempo de compilação dynamic
, a expressão será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão é dynamic
e a resolução descrita abaixo ocorrerá em run-time usando-se o tipo de run-time desses operandos que têm o tipo de tempo de compilação dynamic
.
Para uma operação do formato x << count
or x >> count
, a resolução de sobrecarga do operador binário (§12.4.5) é aplicada para selecionar uma implementação de operador específica. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Ao declarar um operador de deslocamento sobrecarregado, o tipo do primeiro operando sempre será a classe ou estrutura que declara o operador, e o tipo do segundo operando sempre será int
.
Os operadores de deslocamento predefinidos estão listados abaixo.
Deslocar à esquerda:
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);
O operador
<<
deslocax
para a esquerda por um número de bits calculado conforme descrito abaixo.Os bits de alta ordem fora do intervalo do tipo de resultado de
x
são descartados, os bits restantes são deslocados para a esquerda e as posições vazias dos bits de baixa ordem são preenchidas com zero.Deslocar à direita:
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);
O operador
>>
deslocax
à direita por um número de bits calculado, conforme descrito abaixo.Quando
x
é do tipoint
oulong
, os bits de ordem baixa dex
são descartados, os bits restantes são deslocados para a direita e as posições de bits vazias de ordem alta são definidas como zero sex
não for negativo e definido como um sex
for negativo.Quando
x
é do tipouint
ouulong
, os bits de ordem baixa dex
são descartados, os bits restantes são deslocados para a direita e as posições de bits vazias de ordem alta são definidas como zero.
Para os operadores predefinidos, o número de bits a serem deslocados é computado da seguinte maneira:
- Quando o tipo de
x
éint
ouuint
, a contagem de deslocamentos é fornecida pelos cinco bits de ordem inferior decount
. Em outras palavras, a contagem de deslocamentos é computada decount & 0x1F
. - Quando o tipo de
x
élong
ouulong
, a contagem de deslocamentos é fornecida pelos seis bits de ordem inferior decount
. Em outras palavras, a contagem de deslocamentos é computada decount & 0x3F
.
Se a contagem de deslocamentos resultante for zero, os operadores de deslocamento simplesmente retornarão o valor de x
.
As operações de deslocamento nunca causam estouros e produzem os mesmos resultados nos contextos marcados e desmarcados.
Quando o operando esquerdo do operador >>
é de um tipo integral assinado, o operador executa um deslocamento aritmético para a direita em que o valor do bit mais significativo (o bit de sinal) do operando é propagado para as posições de bit vazias de ordem superior. Quando o operando esquerdo do operador >>
é de um tipo integral não assinado, o operador executa um deslocamento lógico para a direita, em que as posições de bits vazias de ordem superior são sempre definidas como zero. Para executar a operação oposta daquela inferida do tipo de operando, conversões explícitas podem ser usadas.
Exemplo: se
x
for uma variável do tipoint
, a operaçãounchecked ((int)((uint)x >> y))
executará um deslocamento lógico à direita dex
. fim do exemplo
As formas elevadas (§12.4.8) dos operadores de deslocamento predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12 Operadores de teste de tipo e relacional
12.12.1 Geral
Os operadores ==
, !=
, <
, >
, <=
, >=
, is
e as
são chamados de operadores relacionais e de teste de tipo.
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression '<=' shift_expression
| relational_expression '>=' shift_expression
| relational_expression 'is' type
| relational_expression 'is' pattern
| relational_expression 'as' type
;
equality_expression
: relational_expression
| equality_expression '==' relational_expression
| equality_expression '!=' relational_expression
;
Observação: a pesquisa pelo operando direito do operador
is
deve primeiro ser testada como um tipo, e depois como uma expressão que pode abranger vários tokens. No caso em que o operando é uma expressão, a expressão padrão deve ter precedência pelo menos tão alta quanto shift_expression. fim da observação
O operador is
é descrito em §12.12.12 e o operador as
é descrito em §12.12.13.
Os operadores ==
, !=
, <
, >
, <=
e >=
são operadores de comparação.
Se default_literal (§12.8.21) for usado como operando de um operador <
, >
, <=
, or >=
, ocorrerá um erro de tempo de compilação.
Se um default_literal for usado como os dois operandos de um operador ==
ou !=
, um erro de tempo de compilação ocorrerá. Se default_literal for usado como o operando esquerdo do operador is
ou as
, ocorrerá um erro de tempo de compilação.
Se um operando de um operador de comparação tiver o tipo de tempo de compilação dynamic
, a expressão será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão é dynamic
e a resolução descrita abaixo ocorrerá em run-time usando-se o tipo de run-time desses operandos que têm o tipo de tempo de compilação dynamic
.
Para uma operação do formato x «op» y
, em que «op» é um operador de comparação, a resolução de sobrecarga (§12.4.5) é aplicada para selecionar uma implementação de operador específico. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador. Se os dois operandos de uma equality_expression forem o literal null
, a resolução de sobrecarga não será executada e a expressão será avaliada como um valor constante de true
ou false
de acordo com se o operador é ==
ou !=
.
Os operadores de comparação predefinidos são descritos nas subcláusula a seguir. Todos os operadores de comparação predefinidos retornam um resultado do tipo bool, conforme descrito na tabela a seguir.
Operação | Resultado |
---|---|
x == y |
true se x for igual a y ; caso contrário, false |
x != y |
true se x não for igual a y ; caso contrário, false . |
x < y |
true se x for menor que y , caso contrário, false |
x > y |
true se x for maior que y , caso contrário, false |
x <= y |
true se x for menor ou igual a y , caso contrário, false |
x >= y |
true se x for maior ou igual a y , caso contrário, false |
12.12.2 Operadores de comparação de inteiros
Os operadores de comparação de inteiro predefinidos são:
bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);
bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);
bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);
bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);
bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);
Cada um desses operadores compara os valores numéricos dos dois operandos inteiros e retorna um valor bool
que indica se a relação específica é true
ou false
.
As formas elevadas (§12.4.8) dos operadores de comparação de inteiros predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12.3 Operadores de comparação de ponto flutuante
Os operadores de comparação de ponto flutuante predefinidos são:
bool operator ==(float x, float y);
bool operator ==(double x, double y);
bool operator !=(float x, float y);
bool operator !=(double x, double y);
bool operator <(float x, float y);
bool operator <(double x, double y);
bool operator >(float x, float y);
bool operator >(double x, double y);
bool operator <=(float x, float y);
bool operator <=(double x, double y);
bool operator >=(float x, float y);
bool operator >=(double x, double y);
Os operadores comparam os operandos de acordo com as regras do padrão IEC 60559:
Se um dos operandos for NaN, o resultado será false
para todos os operadores, exceto !=
, para o qual o resultado será true
. Para dois operandos, x != y
sempre produz o mesmo resultado que !(x == y)
. No entanto, quando um ou os dois operandos são NaN, os operadores <
, >
, <=
e >=
não produzem os mesmos resultados que a negação lógica do operador oposto.
Exemplo: se
x
ouy
for NaN,x < y
seráfalse
, mas!(x >= y)
serátrue
. fim do exemplo
Quando nenhum operando é NaN, os operadores comparam os valores dos dois operandos de ponto flutuante em relação à ordenação
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
em que min
e max
são os menores e os maiores valores finitos positivos que podem ser representados no formato de ponto flutuante fornecido. Os efeitos notáveis dessa ordenação são:
- Zeros negativos e positivos são considerados iguais.
- Um infinito negativo é considerado menor que todos os outros valores, mas igual a outro infinito negativo.
- Um infinito positivo é considerado maior do que todos os outros valores, mas igual a outro infinito positivo.
As formas elevadas (§12.4.8) dos operadores de comparação de pontos flutuantes predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12.4 Operadores de comparação decimal
Os operadores de comparação decimal predefinidos são:
bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);
Cada um desses operadores compara os valores numéricos dos dois operandos decimais e retorna um valor bool
que indica se a relação específica é true
ou false
. Cada comparação decimal é equivalente ao uso do operador relacional ou de igualdade correspondente do tipo System.Decimal
.
As formas elevadas (§12.4.8) dos operadores de comparação de decimais predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12.5 Operadores de igualdade booliana
Os operadores de igualdade booliana predefinidos são:
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
O resultado de ==
será true
se x
e y
estiverem true
ou se x
e y
estiverem false
. Caso contrário, o resultado será false
.
O resultado de !=
será false
se x
e y
estiverem true
ou se x
e y
estiverem false
. Caso contrário, o resultado será true
. Quando os operandos são do tipo bool
, o operador !=
produz o mesmo resultado que o operador ^
.
As formas elevadas (§12.4.8) dos operadores de igualdade booleana predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12.6 Operadores de comparação de enumeração
Cada tipo de enumeração fornece implicitamente os seguintes operadores de comparação predefinidos
bool operator ==(E x, E y);
bool operator !=(E x, E y);
bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);
O resultado da avaliação de x «op» y
, em que x e y são expressões de um tipo de enumeração E
com um tipo subjacente U
e «op» é um dos operadores de comparação, é exatamente o mesmo da avaliação de ((U)x) «op» ((U)y)
. Em outras palavras, os operadores de comparação de tipo de enumeração simplesmente comparam os valores integrais subjacentes dos dois operandos.
As formas elevadas (§12.4.8) dos operadores de comparação de enumeração predefinidos não elevados anteriormente mencionados também são predefinidas.
12.12.7 Operadores de igualdade em tipos de referência
Cada tipo de classe C
implicitamente fornece os seguintes operadores de igualdade de tipo de referência predefinidos:
bool operator ==(C x, C y);
bool operator !=(C x, C y);
a menos que existam operadores de igualdade predefinidos para C
(por exemplo, quando C
é string
ou System.Delegate
).
Os operadores retornam o resultado da comparação das duas referências para igualdade ou não igualdade.
operator ==
retorna true
se e somente se x
e y
se referirem à mesma instância ou forem os dois null
, enquanto operator !=
retornará true
se e somente se operator ==
com os mesmos operandos retornarem false
.
Além das regras normais de aplicabilidade (§12.6.4.2), os operadores de igualdade de tipo de referência predefinidos exigem uma das duas seguintes situações para serem aplicáveis:
- Os dois operandos são um valor de um tipo conhecido por ser um reference_type ou o literal
null
. Além disso, uma conversão de identidade ou referência explícita (§10.3.5) existe de um operando para o tipo do outro operando. - Um operando é o literal
null
e o outro operando é um valor do tipoT
em queT
é um type_parameter que não é conhecido por ser um tipo de valor e não tem a restrição de tipo de valor.- Se em run-time
T
for um tipo de valor não anulável, o resultado de==
seráfalse
e o resultado de!=
serátrue
. - Se em run-time
T
for um tipo de valor anulável, o resultado será computado da propriedadeHasValue
do operando, conforme descrito em (§12.12.10). - Se, em tempo de execução,
T
for um tipo de referência, o resultado serátrue
se o operando fornull
; caso contrário, seráfalse
.
- Se em run-time
A menos que uma dessas condições seja verdadeira, ocorrerá um erro de tempo de associação.
Observação: as implicações notáveis dessas regras são:
- É um erro de tempo de associação usar os operadores de igualdade de tipo de referência predefinidos para comparar duas referências que são conhecidas por serem diferentes em tempo de associação. Por exemplo, se os tipos de tempo de associação dos operandos forem dois tipos de classe e se nenhum deles derivar do outro, então seria impossível para os dois operandos fazerem referência ao mesmo objeto. Portanto, a operação será considerada um erro de tempo de associação.
- Os operadores de igualdade de tipo de referência predefinidos não permitem que operandos de tipo de valor sejam comparados (exceto quando os parâmetros de tipo são comparados a
null
, que é tratado de forma especial).- Operandos de operadores de igualdade de tipo de referência predefinidos nunca são delimitados. Não teria sentido executar tais operações boxing, pois as referências às instâncias boxed recém-alocadas seriam necessariamente diferentes de todas as outras referências.
Para uma operação de formato
x == y
oux != y
, se houver algumoperator ==
ouoperator !=
definido pelo usuário aplicável, as regras de resolução de sobrecarga de (§12.4.5) selecionarão aquele operador em vez do operador de igualdade de tipo de referência predefinido. Sempre é possível selecionar o operador de igualdade de tipo de referência predefinido convertendo explicitamente um ou ambos os operandos para o tipoobject
.fim da observação
Exemplo: O exemplo a seguir verifica se um argumento de um tipo de parâmetro de tipo não restringido é
null
.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }
O constructo
x == null
é permitido mesmo queT
possa representar um tipo de valor não anulável e o resultado seja simplesmente definido comofalse
quandoT
for um tipo de valor não anulável.fim do exemplo
Para uma operação de formato x == y
ou x != y
, se houver algumaoperator ==
ou operator !=
aplicável, as regras de resolução de sobrecarga do operador (§12.4.5) selecionarão esse operador em vez do operador de igualdade de tipo de referência predefinido.
Observação: É sempre possível selecionar o operador de igualdade de tipo de referência predefinido convertendo explicitamente ambos os operandos para o tipo
object
. fim da observação
Exemplo: o exemplo
class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }
produz a saída
True False False False
As variáveis
s
et
referem-se a duas instâncias de cadeia de caracteres distintas que contêm os mesmos caracteres. A primeira comparação geraTrue
porque o operador de igualdade de cadeia de caracteres predefinido (§12.12.8) é selecionado quando os dois operandos são do tipostring
. Todas as comparações restantes produzemFalse
porque a sobrecarga deoperator ==
no tipostring
não é aplicável quando qualquer dos operandos tem um tipo de tempo de associação deobject
.Observe que a técnica acima não é significativa para tipos de valor. O exemplo
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }
resulta em
False
porque as conversões criam referências a duas instâncias separadas de valoresint
boxed.fim do exemplo
12.12.8 Operadores de igualdade de cadeia de caracteres
Os operadores de igualdade de cadeia de caracteres predefinidos são:
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Dois valores string
são considerados iguais quando um dos seguintes valores é verdadeiro:
- Os dois valores são
null
. - Os dois valores são referências não
null
a instâncias de cadeia de caracteres que têm comprimentos idênticos e caracteres idênticos em cada posição de caractere.
Os operadores de igualdade de cadeia de caracteres comparam valores de cadeia de caracteres em vez de referências de cadeia de caracteres. Quando duas instâncias de cadeia de caracteres separadas contêm exatamente a mesma sequência de caracteres, os valores das cadeias de caracteres são iguais, mas as referências são diferentes.
Observação: conforme descrito em §12.12.7, os operadores de igualdade de tipo de referência podem ser usados para comparar referências de cadeia de caracteres em vez de valores de cadeia de caracteres. fim da observação
12.12.9 Operadores de igualdade de delegado
Os operadores de igualdade de delegado predefinidos são:
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
Duas instâncias de delegado são consideradas iguais da seguinte maneira:
- Se uma ou as duas instâncias delegadas forem
null
, as instâncias serão consideradas iguais se e somente se as duas foremnull
. - Se os delegados tiverem tipos de tempo de execução diferentes, eles nunca serão iguais.
- Se ambas as instâncias de delegado tiverem uma lista de invocação (§20.2), essas instâncias serão iguais se e somente se suas listas de invocação tiverem o mesmo tamanho e cada entrada na lista de invocação for igual (conforme definido abaixo) à entrada correspondente, em ordem, na lista de invocação da outra.
As regras a seguir regem a igualdade de entradas da lista de invocação:
- Se duas entradas da lista de invocação se referirem ao mesmo método estático, as entradas serão iguais.
- Se duas entradas da lista de invocação se referirem ao mesmo método não estático no mesmo objeto de destino (conforme definido pelos operadores de igualdade de referência), as entradas serão iguais.
- As entradas da lista de invocação produzidas a partir da avaliação de funções anônimas semanticamente idênticas (§12.19) com o mesmo conjunto (possivelmente vazio) de instâncias de variável externa capturadas são permitidas (mas não necessárias) iguais.
Se a resolução de sobrecarga do operador for resolvida para um operador de igualdade de delegado e os tipos de tempo de associação dos dois operandos forem tipos de delegados, conforme descrito em §20 em vez de System.Delegate
, e não houver nenhuma conversão de identidade entre os tipos de operando do tipo de associação, ocorrerá um erro de tempo de associação.
Observação: essa regra impede comparações que não podem considerar valores diferentes de
null
como iguais, pois são referências a instâncias de tipos diferentes de delegados. fim da observação
12.12.10 Operadores de igualdade entre tipos de valor anuláveis e o literal nulo
Os operadores ==
e !=
permitem que um operando seja um valor de um tipo de valor anulável e o outro seja o literal null
, mesmo que não exista nenhum operador predefinido ou definido pelo usuário (em formato não elevado ou elevado) para a operação.
Para uma operação de uma das formas
x == null null == x x != null null != x
em que x
é uma expressão de um tipo de valor anulável, se a resolução de sobrecarga do operador (§12.4.5) não encontrar um operador aplicável, o resultado será computado da propriedade HasValue
de x
. Especificamente, os dois primeiros formatos são convertidos em !x.HasValue
e os dois últimos formatos são convertidos em x.HasValue
.
12.12.11 Operadores de igualdade de tupla
Os operadores de igualdade de tupla são aplicados em par aos elementos dos operandos de tupla em ordem lexical.
Se cada operando x
e y
de um operador de ==
ou !=
for classificado como uma tupla ou como um valor com um tipo de tupla (§8.3.11), o operador será um operador de igualdade de tupla.
Se um operando e
for classificado como uma tupla, os elementos e1...en
serão os resultados da avaliação das expressões de elemento da expressão de tupla. Caso contrário, se e
for um valor de tipo tupla, os elementos deverão ser t.Item1...t.Itemn
, onde t
é o resultado da avaliação de e
.
Os operandos x
e y
de um operador de igualdade de tupla devem ter a mesma aridade, ou ocorrerá um erro de tempo de compilação. Para cada par de elementos xi
e yi
, o mesmo operador de igualdade deve ser aplicado e produzirá um resultado do tipo bool
, dynamic
, um tipo que tem uma conversão implícita em bool
ou um tipo que define os operadores true
e false
.
O operador de igualdade de tupla x == y
é avaliado da seguinte maneira:
- O operando do lado esquerdo
x
é avaliado. - O operando do lado direito
y
é avaliado. - Para cada par de elementos
xi
eyi
em ordem lexical:- O operador
xi == yi
é avaliado e um resultado do tipobool
é obtido da seguinte maneira:- Se a comparação produziu
bool
, esse será o resultado. - Caso contrário, se a comparação produziu
dynamic
, o operadorfalse
será invocado dinamicamente nele e o valorbool
resultante será negado com o operador de negação lógica (!
). - Caso contrário, se o tipo da comparação tiver uma conversão implícita para
bool
, essa conversão será aplicada. - Caso contrário, se o tipo da comparação tiver um operador
false
, esse operador será invocado e o valorbool
resultante será negado com o operador de negação lógica (!
).
- Se a comparação produziu
- Se o
bool
resultante forfalse
, nenhuma avaliação adicional ocorrerá, e o resultado do operador de igualdade entre tuplas seráfalse
.
- O operador
- Se todas as comparações de elementos produzirem
true
, o resultado do operador de igualdade de tupla serátrue
.
O operador de igualdade de tupla x != y
é avaliado da seguinte maneira:
- O operando do lado esquerdo
x
é avaliado. - O operando do lado direito
y
é avaliado. - Para cada par de elementos
xi
eyi
em ordem lexical:- O operador
xi != yi
é avaliado e um resultado do tipobool
é obtido da seguinte maneira:- Se a comparação produziu
bool
, esse será o resultado. - Caso contrário, se a comparação produziu
dynamic
, o operadortrue
será invocado dinamicamente nele e o valorbool
resultante será o resultado. - Caso contrário, se o tipo da comparação tiver uma conversão implícita para
bool
, essa conversão será aplicada. - Caso contrário, se o tipo da comparação tiver um operador
true
, esse operador será invocado e o valorbool
resultante será o resultado.
- Se a comparação produziu
- Se o
bool
resultante fortrue
, nenhuma avaliação adicional ocorrerá, e o resultado do operador de igualdade entre tuplas serátrue
.
- O operador
- Se todas as comparações de elementos produzirem
false
, o resultado do operador de igualdade de tupla seráfalse
.
12.12.12 Operador is
Há dois formatos do operador is
. Um deles é o operador is-type, que tem um tipo no lado direito. O outro é o operador is-pattern, que tem um padrão no lado direito.
12.12.12.1 Operador is-type
O operador is-type é usado para verificar se o tipo de run-time de um objeto é compatível com um tipo determinado. A verificação é executada em run-time. O resultado da operação E is T
, em que E
é uma expressão e T
é um tipo diferente de dynamic
, é um valor booleano que indica se E
não é nulo e pode ser convertido com sucesso para o tipo T
por uma conversão de referência, uma conversão de encaixotamento, uma conversão de desencaixotamento, uma conversão de encapsulamento ou uma conversão de desencapsulamento.
A operação é avaliada como segue:
- Se
E
for uma função anônima ou um grupo de métodos, ocorrerá um erro de tempo de compilação. - Se
E
for o literalnull
ou se o valor deE
fornull
, o resultado seráfalse
. - Ou:
- Deixe
R
ser o tipo de run-time deE
. - Deixe
D
ser derivado deR
da seguinte maneira: - Se
R
for um tipo de valor anulável,D
será o tipo subjacente deR
. - Caso contrário,
D
éR
. - O resultado depende de
D
eT
da seguinte maneira: - Se
T
for um tipo de referência, o resultado serátrue
se:- existe uma conversão de identidade entre
D
eT
, -
D
é um tipo de referência e uma conversão de referência implícita deD
paraT
existe ou - Ou:
D
é um tipo de valor e há uma conversão de boxing deD
emT
.
Ou:D
é um tipo de valor eT
é um tipo de interface implementado porD
.
- existe uma conversão de identidade entre
- Se
T
for um tipo de valor anulável, o resultado serátrue
seD
for o tipo subjacente deT
. - Se
T
for um tipo de valor não anulável, o resultado serátrue
seD
eT
forem do mesmo tipo. - Caso contrário, o resultado será
false
.
Conversões definidas pelo usuário não são consideradas pelo operador is
.
Observação: como o operador
is
é avaliado em run-time, todos os argumentos de tipo foram substituídos e não há tipos abertos (§8.4.3) a serem considerados. fim da observação
Observação: o operador
is
pode ser compreendido em termos de conversões e tipos de tempo de compilação da seguinte maneira, em queC
é o tipo de tempo de compilação deE
:
- Se o tipo de tempo de compilação de
e
for o mesmo queT
ou se uma conversão de referência implícita (§10.2.8), conversão de boxing (§10.2.9), conversão de encapsulamento (§10.6) ou uma conversão de desencapsulamento explícita (§10.6) existir do tipo de tempo de compilação deE
paraT
:
- Se
C
for de um tipo de valor não anulável, o resultado da operação serátrue
.- Caso contrário, o resultado da operação será equivalente à avaliação de
E != null
.- Caso contrário, se uma conversão de referência explícita (§10.3.5) ou conversão de unboxing (§10.3.7) existir de
C
paraT
, ou seC
ouT
for um tipo aberto (§8.4.3), as verificações em run-time como acima deverão ser realizadas.- Caso contrário, nenhuma conversão de referência, boxing, encapsulamento ou desencapsulamento de
E
para o tipoT
será possível, e o resultado da operação seráfalse
. Um compilador pode implementar otimizações com base no tipo de tempo de compilação.fim da observação
12.12.12.2 Operador is-pattern
O operador de padrão é usado para verificar se o valor calculado por uma expressão corresponde a um determinado padrão (§11). A verificação é executada em run-time. O resultado do operador is-pattern será verdadeiro se o valor corresponder ao padrão; caso contrário, é falso.
Para uma expressão da forma E is P
, em que E
é uma expressão relacional do tipo T
e P
é um padrão, é um erro de tempo de compilação se qualquer uma das seguintes condições ocorrer:
-
E
não designa um valor ou não tem um tipo. - O padrão
P
não é aplicável (§11.2) ao tipoT
.
12.12.13 Operador as
O operador as
é usado para converter explicitamente um valor em um determinado tipo de referência ou tipo de valor anulável. Ao contrário de uma expressão de conversão (§12.9.7), o operador as
nunca gera uma exceção. Em vez disso, se não for possível realizar a conversão indicada, o valor resultante será null
.
Em uma operação do formato E as T
, E
deve ser uma expressão e T
deve ser um tipo de referência, um parâmetro de tipo conhecido como um tipo de referência ou um tipo de valor anulável. Além disso, pelo menos um das seguintes situações deve ser verdadeira ou, caso contrário, ocorrerá um erro de tempo de compilação:
- Uma conversão de identidade (§10.2.2), anulável implícita (§10.2.6), de referência implícita (§10.2.8), boxing (§10.2.9), anulável explícita (§10.3.4), de referência explícita (§10.3.5) ou de encapsulamento (§8.3.12) existe de
E
paraT
. - O tipo de
E
ouT
é um tipo aberto. -
E
é o literalnull
.
Se o tipo de tempo de compilação de E
não for dynamic
, a operação E as T
produzirá o mesmo resultado que
E is T ? (T)(E) : (T)null
exceto que E
é avaliado apenas uma vez. Espera-se que um compilador otimize E as T
para executar no máximo uma verificação de tipo em tempo de execução em vez das duas verificações de tipo em tempo de execução implícitas pela expansão acima.
Se o tipo de tempo de compilação de E
for dynamic
, ao contrário do operador de conversão, o operador as
não será associado dinamicamente (§12.3.3). Portanto, a expansão nesse caso é:
E is T ? (T)(object)(E) : (T)null
Algumas conversões, como as definidas pelo usuário, não são possíveis com o operador as
e devem ser realizadas usando expressões de conversão.
Exemplo: no exemplo
class X { public string F(object o) { return o as string; // OK, string is a reference type } public T G<T>(object o) where T : Attribute { return o as T; // Ok, T has a class constraint } public U H<U>(object o) { return o as U; // Error, U is unconstrained } }
o parâmetro de tipo
T
deG
é conhecido por ser um tipo de referência, pois tem a restrição de classe. No entanto, o parâmetro de tipoU
deH
não é; portanto, o uso do operadoras
emH
não é permitido.fim do exemplo
12.13 Operadores lógicos
12.13.1 Geral
Os operadores &
, ^
e |
são chamados de operadores lógicos.
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
Se um operando de um operador lógico tiver o tipo de tempo de compilação dynamic
, a expressão será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão é dynamic
e a resolução descrita abaixo ocorrerá em run-time usando-se o tipo de run-time desses operandos que têm o tipo de tempo de compilação dynamic
.
Para uma operação do formato x «op» y
, em que «op» é um dos operadores lógicos, a resolução de sobrecarga (§12.4.5) é aplicada para selecionar uma implementação de operador específico. Os operandos são convertidos nos tipos de parâmetro do operador selecionado e o tipo do resultado é o tipo de retorno do operador.
Os operadores lógicos predefinidos são descritos nas subcláusula a seguir.
12.13.2 Operadores lógicos inteiros
Os operadores lógicos inteiros predefinidos são:
int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);
int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);
int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);
O operador &
computa o AND lógico bit a bit dos dois operandos, o operador |
computa o OR lógico bit a bit dos dois operandos e o operador ^
computa o OR exclusivo lógico bit a bit dos dois operandos. Nenhum transbordamento é possível nessas operações.
As formas elevadas (§12.4.8) dos operadores lógicos de inteiros predefinidos não elevados anteriormente mencionados também são predefinidas.
12.13.3 Operadores lógicos de enumeração
Cada tipo de enumeração E
fornece implicitamente os seguintes operadores lógicos predefinidos:
E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);
O resultado da avaliação dex «op» y
, em que x
e y
são expressões de um tipo de enumeração E
with an underlying type U
e «op» é um dos operadores lógicos, é exatamente o mesmo da avaliação de (E)((U)x «op» (U)y)
. Em outras palavras, os operadores lógicos de tipo de enumeração executam a operação lógica no tipo subjacente dos dois operandos.
As formas elevadas (§12.4.8) dos operadores lógicos de enumeração predefinidos não elevados anteriormente mencionados também são predefinidas.
12.13.4 Operadores lógicos boolianos
Os operadores lógicos boolianos predefinidos são:
bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
O resultado de x & y
será true
se ambos x
e y
forem true
. Caso contrário, o resultado será false
.
O resultado de x | y
será true
se x
ou y
for true
. Caso contrário, o resultado será false
.
O resultado de x ^ y
é true
se x
é true
e y
é false
ou x
é false
e y
é true
. Caso contrário, o resultado será false
. Quando os operandos são do tipo bool
, o operador ^
computa o mesmo resultado que o operador !=
.
12.13.5 Booleano anulável & e | operadores
O tipo booliano anulável bool?
pode representar três valores, true
, false
e null
.
Assim como acontece com os outros operadores binários, os formatos de precisão dos operadores lógicos &
e |
(§12.13.4) também são predefinidos:
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
A semântica dos operadores &
e |
levantados é definida pela tabela a seguir.
x |
y |
x & y |
x \| y |
---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
true |
null |
null |
true |
false |
true |
false |
true |
false |
false |
false |
false |
false |
null |
false |
null |
null |
true |
null |
true |
null |
false |
false |
null |
null |
null |
null |
null |
Observação: o tipo
bool?
é conceitualmente semelhante ao tipo de três valores usado para expressões boolianas no SQL. A tabela acima segue a mesma semântica que o SQL, enquanto a aplicação das regras de §12.4.8 aos operadores&
e|
não. As regras de §12.4.8 já fornecem uma semântica semelhante à do SQL para o operador elevado de^
. fim da observação
12.14 Operadores lógicos condicionais
12.14.1 Geral
Os operadores &&
e ||
são chamados de operadores lógicos condicionais. Eles também são chamados de operadores lógicos de "curto-circuito".
conditional_and_expression
: inclusive_or_expression
| conditional_and_expression '&&' inclusive_or_expression
;
conditional_or_expression
: conditional_and_expression
| conditional_or_expression '||' conditional_and_expression
;
Os operadores &&
e ||
são versões condicionais dos operadores &
e |
:
- A operação
x && y
corresponde à operaçãox & y
, exceto quey
é avaliada somente sex
não forfalse
. - A operação
x || y
corresponde à operaçãox | y
, exceto quey
é avaliada somente sex
não fortrue
.
Observação: o motivo pelo qual o curto-circuito usa as condições 'not true' e 'not false' é para permitir que operadores condicionais definidos pelo usuário definam quando o curto-circuito se aplica. Os tipos definidos pelo usuário podem estar em um estado em que
operator true
retornafalse
eoperator false
retornafalse
. Nesses casos, nem&&
nem||
darão curto-circuito. fim da observação
Se um operando de um operador lógico condicional tiver o tipo de tempo de compilação dynamic
, a expressão será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão é dynamic
e a resolução descrita abaixo ocorrerá em run-time usando-se o tipo de run-time desses operandos que têm o tipo de tempo de compilação dynamic
.
Uma operação do formato x && y
ou x || y
é processada aplicando-se a resolução de sobrecarga (§12.4.5) como se a operação tivesse sido escrita como x & y
ou x | y
. E, em seguida,
- Se a resolução de sobrecarga não encontrar um único operador melhor ou se a resolução de sobrecarga selecionar um dos operadores lógicos inteiros predefinidos ou operadores lógicos boolianos anuláveis (§12.13.5), ocorrerá um erro de tempo de associação.
- Caso contrário, se o operador selecionado for um dos operadores lógicos boolianos predefinidos (§12.13.4), a operação será processada conforme descrito em §12.14.2.
- Caso contrário, o operador selecionado será um operador definido pelo usuário e a operação será processada conforme descrito em §12.14.3.
Não é possível sobrecarregar diretamente os operadores lógicos condicionais. No entanto, como os operadores lógicos condicionais são avaliados em termos de operadores lógicos regulares, as sobrecargas dos operadores lógicos regulares também são, sob certas restrições, consideradas sobrecargas dos operadores lógicos condicionais. Isso é descrito mais adiante em §12.14.3.
12.14.2 Operadores lógicos condicionais boolianos
Quando os operandos de &&
ou ||
são do tipo bool
ou quando os operandos são de tipos que não definem um operator &
ou operator |
aplicável, mas definem conversões implícitas para bool
, a operação é processada da seguinte maneira:
- A operação
x && y
é avaliada comox ? y : false
. Em outras palavras,x
é primeiro avaliado e convertido para o tipobool
. Em seguida, sex
fortrue
,y
será avaliado e convertido para o tipobool
, e isso se torna o resultado da operação. Caso contrário, o resultado da operação seráfalse
. - A operação
x || y
é avaliada comox ? true : y
. Em outras palavras,x
é primeiro avaliado e convertido para o tipobool
. Então, sex
fortrue
, o resultado da operação serátrue
. Caso contrário,y
é avaliado e convertido no tipobool
e isso se torna o resultado da operação.
12.14.3 Operadores lógicos condicionais definidos pelo usuário
Quando os operandos de &&
ou ||
são de tipos que declaram um operator &
ou operator |
definidos pelo usuário aplicável, as duas seguintes situações devem ser verdadeiras, em que T
é o tipo no qual o operador selecionado é declarado:
- O tipo de retorno e o tipo de cada parâmetro do operador selecionado devem ser
T
. Em outras palavras, o operador deve computar o AND lógico ou o OR lógico de dois operandos do tipoT
e retornará um resultado do tipoT
. -
T
deve conter declarações deoperator true
eoperator false
.
Um erro de tempo de vinculação ocorrerá se um desses requisitos não for atendido. Caso contrário, a operação &&
ou ||
é avaliada combinando-se o operator true
ou operator false
definido pelo usuário com o operador definido pelo usuário selecionado:
- A operação
x && y
é avaliada comoT.false(x) ? x : T.&(x, y)
, em queT.false(x)
é uma invocação dooperator false
declarado emT
eT.&(x, y)
é uma invocação dooperator &
selecionado. Em outras palavras,x
é avaliado primeiro eoperator false
é invocado no resultado para determinar sex
é definitivamente falso. Em seguida, sex
for definitivamente falso, o resultado da operação será o valor computado anteriormente parax
. Caso contrário,y
é avaliado, e ooperator &
selecionado é invocado com o valor previamente calculado parax
e o valor computado paray
, produzindo o resultado da operação. - A operação
x || y
é avaliada comoT.true(x) ? x : T.|(x, y)
, em queT.true(x)
é uma invocação dooperator true
declarado emT
eT.|(x, y)
é uma invocação dooperator |
selecionado. Em outras palavras,x
é avaliado primeiro eoperator true
é invocado no resultado para determinar sex
é definitivamente verdadeiro. Em seguida, sex
for definitivamente verdadeiro, o resultado da operação será o valor computado anteriormente parax
. Caso contrário,y
é avaliado, e ooperator |
selecionado é invocado com o valor previamente calculado parax
e o valor computado paray
, produzindo o resultado da operação.
Em qualquer uma dessas operações, a expressão fornecida por x
é avaliada apenas uma vez e a expressão fornecida por y
não é avaliada ou avaliada apenas uma vez.
12.15 O operador de coalescência nula
O operador ??
é chamado operador de coalescência nula.
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
Em uma expressão de associação nula do formato a ?? b
, se a
não fornull
, o resultado será a
; caso contrário, o resultado será b
. A operação avaliará b
somente se a
for null
.
O operador de coalescência nula é associativo à direita, o que significa que as operações são agrupadas da direita para a esquerda.
Exemplo: uma expressão do formato
a ?? b ?? c
é avaliada comoa ?? (b ?? c)
. Em termos gerais, uma expressão do formatoE1 ?? E2 ?? ... ?? EN
retorna o primeiro dos operandos que é nãonull
ounull
se todos os operandos foremnull
. fim do exemplo
O tipo da expressão a ?? b
depende de quais conversões implícitas estão disponíveis nos operandos. Em ordem de preferência, o tipo de a ?? b
é A₀
, A
ou B
, em que A
é o tipo de a
(desde que a
tenha um tipo), B
é o tipo de b
(desde que b
tenha um tipo) e A₀
seja o tipo subjacente de A
se A
for um tipo de valor anulável ou A
. Especificamente, a ?? b
é processado da seguinte maneira:
- Se
A
existir e for um tipo não gerenciado (§8.8) ou conhecido por ser um tipo de valor não anulável, ocorrerá um erro de tempo de compilação. - Caso contrário, se
A
existir eb
for uma expressão dinâmica, o tipo de resultado serádynamic
. Em run-time,a
é avaliado primeiro. Sea
não fornull
,a
será convertido emdynamic
e isso se tornará o resultado. Caso contrário,b
será avaliado e esse será o resultado. - Caso contrário, se
A
existir e for um tipo de valor anulável e existir uma conversão implícita deb
paraA₀
, o tipo de resultado seráA₀
. Em run-time,a
é avaliado primeiro. Sea
não fornull
,a
será desembalhado para o tipoA₀
, e isso será o resultado. Caso contrário,b
é avaliado e convertido no tipoA₀
, o que se torna o resultado. - Caso contrário, se
A
existir e houver uma conversão implícita deb
paraA
, o tipo de resultado seráA
. Em run-time,a
é avaliado primeiro. Sea
não fornull
,a
será o resultado. Caso contrário,b
é avaliado e convertido no tipoA
, o que se torna o resultado. - Caso contrário, se
A
existir e for um tipo de valor anulável,b
tiver um tipoB
e existir uma conversão implícita deA₀
paraB
, o tipo de resultado seráB
. Em run-time,a
é avaliado primeiro. Sea
não fornull
,a
será desembrulhado para o tipoA₀
e convertido no tipoB
, e isso se torna o resultado. Caso contrário,b
será avaliado e se tornará o resultado. - Caso contrário, se
b
tiver um tipoB
e houver uma conversão implícita dea
paraB
, o tipo de resultado seráB
. Em run-time,a
é avaliado primeiro. Sea
não fornull
,a
é convertido para o tipoB
e isso se torna o resultado. Caso contrário,b
será avaliado e se tornará o resultado. - Caso contrário,
a
eb
são incompatíveis e ocorre um erro de tempo de compilação.
Exemplo:
T M<T>(T a, T b) => a ?? b; string s = M(null, "text"); int i = M(19, 23);
O parâmetro
T
de tipo para o métodoM
não é restrito. Portanto, o argumento de tipo pode ser um tipo de referência ou um tipo de valor anulável, conforme mostrado na primeira chamada paraM
. O argumento de tipo também pode ser um tipo de valor não anulável, conforme mostrado na segunda chamada paraM
. Quando o argumento de tipo é um tipo de valor não anulável, o valor da expressãoa ?? b
éa
.fim do exemplo
12.16 Operador de expressão throw
throw_expression
: 'throw' null_coalescing_expression
;
Uma throw_expression lança o valor produzido avaliando a null_coalescing_expression. A expressão deve ser implicitamente conversível para System.Exception
e o resultado da avaliação da expressão será convertido em System.Exception
antes de ser lançada. O comportamento em run-time da avaliação de uma expressão de lançamento é o mesmo especificado para uma instrução de lançamento (§13.10.6).
Uma throw_expression não tem tipo. Uma throw_expression é conversível para todos os tipos por uma conversão implícita de lançamento.
Uma expressão de lançamento só deve ocorrer nos seguintes contextos sintáticos:
- Como o segundo ou terceiro operando de um operador condicional ternário (
?:
). - Como o segundo operando de um operador de coalescência de nulos (
??
). - Como o corpo de um lambda ou membro com corpo de expressão.
12.17 Expressões de declaração
Uma expressão de declaração declara uma variável local.
declaration_expression
: local_variable_type identifier
;
local_variable_type
: type
| 'var'
;
simple_name_
também é considerado uma expressão de declaração se a pesquisa de nome simples não encontrar uma declaração associada (§12.8.4). Quando usado como uma expressão de declaração, _
é chamado de descartável simples. É semanticamente equivalente a var _
, mas pode-se usar em mais lugares.
Uma declaração de lançamento só deve ocorrer nos seguintes contextos sintáticos:
- Como um
out
argument_value em uma argument_list. - Como um simples descarte
_
compreendendo o lado esquerdo de uma atribuição simples (§12.21.2). - Como um tuple_element em uma ou mais tuple_expressions aninhadas recursivamente, sendo a mais externa aquela que compreende o lado esquerdo de uma atribuição de desconstrução. Uma deconstruction_expression dá origem a expressões de declaração nessa posição, embora as expressões de declaração não estejam sintaticamente presentes.
Observação: isso significa que uma expressão de declaração não pode ser colocada entre parênteses. fim da observação
É um erro que uma variável tipada implicitamente declarada com uma declaration_expression seja referenciada na argument_list em que é declarada.
É um erro para uma variável declarada com uma declaration_expression ser referenciada na atribuição de desconstrução em que ela ocorre.
Uma expressão de declaração que é um descarte simples ou em que o local_variable_type é o identificador var
é classificado como uma variável tipada implicitamente. A expressão não tem tipo e o tipo da variável local é inferido com base no contexto sintático da seguinte maneira:
- Em argument_list o tipo inferido da variável é o tipo declarado do parâmetro correspondente.
- Como o lado esquerdo de uma atribuição simples, o tipo inferido da variável é o tipo do lado direito da atribuição.
- Em uma tuple_expression no lado esquerdo de uma atribuição simples, o tipo inferido da variável é o tipo do elemento de tupla correspondente no lado direito (após a desconstrução) da atribuição.
Caso contrário, a expressão de declaração será classificada como uma variável tipada explicitamente e o tipo da expressão, bem como a variável declarada, será aquele fornecido pelo local_variable_type.
Uma expressão de declaração com o identificador _
é um descarte (§9.2.9.2) e não introduz um nome para a variável. Uma expressão de declaração com um identificador diferente de _
insere esse nome no espaço mais próximo de declaração de variáveis locais (§7.3).
Exemplo:
string M(out int i, string s, out bool b) { ... } var s1 = M(out int i1, "One", out var b1); Console.WriteLine($"{i1}, {b1}, {s1}"); // Error: i2 referenced within declaring argument list var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); var s3 = M(out int _, "Three", out var _);
A declaração de
s1
mostra expressões de declaração tipadas explícita e implicitamente. O tipo inferido deb1
ébool
porque esse é o tipo do parâmetro de saída correspondente emM1
. OWriteLine
subsequente é capaz de acessari1
eb1
, que foram introduzidos no escopo delimitador.A declaração de
s2
mostra uma tentativa de usari2
na chamada aninhada paraM
, o que não é permitido, porque a referência ocorre na lista de argumentos em quei2
foi declarado. Por outro lado, a referência ab2
no argumento final é permitida, pois ocorre após o final da lista de argumentos aninhados em queb2
foi declarado.A declaração de
s3
mostra o uso de expressões de declaração implícita e explicitamente tipadas que são descartadas. Como os descartes não declaram uma variável nomeada, são permitidas as múltiplas ocorrências do identificador_
.(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);
Este exemplo demonstra o uso de declarações implícita e explicitamente tipadas para variáveis e descartes em uma atribuição de desestruturação. simple_name
_
é equivalente avar _
quando não é encontrada nenhuma declaração de_
.void M1(out int i) { ... } void M2(string _) { M1(out _); // Error: `_` is a string M1(out var _); }
Este exemplo mostra o uso de
var _
para fornecer um descarte tipado implicitamente quando_
não estiver disponível, pois designa uma variável no escopo delimitador.fim do exemplo
12.18 Operador condicional
O operador ?:
é chamado operador condicional. Também é chamado, às vezes, de operador ternário.
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' expression ':' expression
| null_coalescing_expression '?' 'ref' variable_reference ':'
'ref' variable_reference
;
Uma expressão de lançamento (§12.16) não será permitida em um operador condicional se ref
estiver presente.
Uma expressão condicional do formato b ? x : y
avalia primeiro a condição b
. Em seguida, se b
for true
, x
será avaliado e se tornará o resultado da operação. Caso contrário, y
será avaliado e se tornará o resultado da operação. Uma expressão condicional nunca avalia x
e y
.
O operador condicional é associativo à direita, o que significa que as operações são agrupadas da direita para a esquerda.
Exemplo: uma expressão do formato
a ? b : c ? d : e
é avaliada comoa ? b : (c ? d : e)
. fim do exemplo
O primeiro operando do operador ?:
deve ser uma expressão que possa ser implicitamente convertida em bool
ou uma expressão de um tipo que implementa operator true
. Se nenhum desses requisitos for atendido, ocorrerá um erro de tempo de compilação.
Se ref
estiver presente:
- Uma conversão de identidade deve existir entre os tipos de dois variable_references, e o tipo do resultado pode ser qualquer tipo. Se um dos tipos for
dynamic
, a inferência de tipo preferirádynamic
(§8.7). Se um dos tipos for um tipo de tupla (§8.3.11), a inferência de tipo incluirá os nomes de elementos quando os nomes de elementos na mesma posição ordinal forem correspondentes nas duas tuplas. - O resultado é uma referência de variável, que é gravável se as duas variable_references forem graváveis.
Observação: quando
ref
está presente, conditional_expression retorna uma referência variável, que pode ser atribuída a uma variável de referência usando-se o operador= ref
ou passada como um parâmetro de referência/entrada/saída. fim da observação
Se ref
não estiver presente, o segundo e o terceiro operandos, respectivamente x
e y
, do operador ?:
controlarão o tipo da expressão condicional:
- Se
x
tiver tipo deX
ey
tiver tipo deY
,- Se houver uma conversão de identidade entre
X
eY
, o resultado será o melhor tipo comum de um conjunto de expressões (§12.6.3.15). Se um dos tipos fordynamic
, a inferência de tipo preferirádynamic
(§8.7). Se um dos tipos for um tipo de tupla (§8.3.11), a inferência de tipo incluirá os nomes de elementos quando os nomes de elementos na mesma posição ordinal forem correspondentes nas duas tuplas. - Caso contrário, se uma conversão implícita (§10.2) existir de
X
paraY
, mas não deY
paraX
,Y
será o tipo da expressão condicional. - Caso contrário, se uma conversão de enumeração implícita (§10.2.4) existir de
X
paraY
,Y
será o tipo da expressão condicional. - Caso contrário, se uma conversão de enumeração implícita (§10.2.4) existir de
Y
paraX
,X
será o tipo da expressão condicional. - Caso contrário, se uma conversão implícita (§10.2) existir de
Y
paraX
, mas não deX
paraY
,X
será o tipo da expressão condicional. - Caso contrário, nenhum tipo de expressão poderá ser determinado e ocorrerá um erro de tempo de compilação.
- Se houver uma conversão de identidade entre
- Se apenas um de
x
ey
tiver um tipo ex
ey
forem implicitamente conversíveis para esse tipo, esse será o tipo da expressão condicional. - Caso contrário, nenhum tipo de expressão poderá ser determinado e ocorrerá um erro de tempo de compilação.
O processamento run-time de uma expressão condicional ref do formato b ? ref x : ref y
consiste nas seguintes etapas:
- Primeiro,
b
é avaliado e o valorbool
deb
é determinado:- Se houver uma conversão implícita do tipo de
b
parabool
, essa conversão implícita será executada para produzir um valorbool
. - Caso contrário,
operator true
definido pelo tipo deb
é invocado para produzir um valorbool
.
- Se houver uma conversão implícita do tipo de
- Se o valor
bool
produzido pela etapa acima fortrue
,x
será avaliado e a referência de variável resultante se tornará o resultado da expressão condicional. - Caso contrário,
y
será avaliado e a referência de variável resultante se tornará o resultado da expressão condicional.
O processamento run-time de uma expressão condicional do formato b ? x : y
consiste nas seguintes etapas:
- Primeiro,
b
é avaliado e o valorbool
deb
é determinado:- Se houver uma conversão implícita do tipo de
b
parabool
, essa conversão implícita será executada para produzir um valorbool
. - Caso contrário,
operator true
definido pelo tipo deb
é invocado para produzir um valorbool
.
- Se houver uma conversão implícita do tipo de
- Se o valor
bool
produzido pela etapa acima fortrue
,x
será avaliado e convertido no tipo da expressão condicional e isso se tornará o resultado da expressão condicional. - Caso contrário,
y
é avaliado e convertido no tipo da expressão condicional e isso se torna o resultado da expressão condicional.
12.19 Expressões de função anônima
12.19.1 Geral
Uma função anônima é uma expressão que representa uma definição de método "em linha". Uma função anônima não tem um valor ou tipo em si mesma, mas é conversível para um delegado ou tipo de árvore de expressão compatível. A avaliação de uma conversão de função anônima depende do tipo de destino da conversão: se for um tipo de delegado, a conversão será avaliada como um valor de delegado que referencia o método definido pela função anônima. Se for um tipo de árvore de expressão, a conversão resulta em uma árvore de expressão que representa a estrutura do método na forma de uma estrutura de objeto.
Observação: por motivos históricos, há dois tipos sintáticos de funções anônimas, ou seja, lambda_expressions e anonymous_method_expressions. Para quase todos os fins, lambda_expressions são mais concisas e expressivas do que anonymous_method_expressions, que permanecem no idioma para retrocompatibilidade. fim da observação
lambda_expression
: 'async'? anonymous_function_signature '=>' anonymous_function_body
;
anonymous_method_expression
: 'async'? 'delegate' explicit_anonymous_function_signature? block
;
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
;
explicit_anonymous_function_signature
: '(' explicit_anonymous_function_parameter_list? ')'
;
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter
(',' explicit_anonymous_function_parameter)*
;
explicit_anonymous_function_parameter
: anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
implicit_anonymous_function_signature
: '(' implicit_anonymous_function_parameter_list? ')'
| implicit_anonymous_function_parameter
;
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter
(',' implicit_anonymous_function_parameter)*
;
implicit_anonymous_function_parameter
: identifier
;
anonymous_function_body
: null_conditional_invocation_expression
| expression
| 'ref' variable_reference
| block
;
Ao identificar um anonymous_function_body, se as alternativas null_conditional_invocation_expression e expression forem aplicáveis, deve-se escolher a primeira.
Observação: a sobreposição e a prioridade entre alternativas aqui é exclusivamente para conveniência descritiva; podem ser elaboradas regras gramaticais para remover a sobreposição. ANTLR e outros sistemas gramaticais adotam a mesma conveniência e, portanto, anonymous_function_body tem a semântica especificada automaticamente. fim da observação
Observação: quando tratada como uma expressão, um formato sintático como
x?.M()
seria um erro se o tipo de resultado deM
forvoid
(§12.8.13). Mas quando tratado como uma null_conditional_invocation_expression, o tipo de resultado pode servoid
. fim da observação
Exemplo: o tipo de resultado de
List<T>.Reverse
évoid
. No código a seguir, o corpo da expressão anônima é uma null_conditional_invocation_expression, portanto, não é um erro.Action<List<int>> a = x => x?.Reverse();
fim do exemplo
O operador =>
tem a mesma precedência da atribuição (=
) e é associativo à direita.
Uma função anônima com o async
modificador é uma função assíncrona e segue as regras descritas em §15.14.
Os parâmetros de uma função anônima na forma de uma expressão lambda podem ser explicitamente ou implicitamente tipados. Em uma lista de parâmetros explicitamente tipada, o tipo de cada parâmetro é explicitamente indicado. Em uma lista de parâmetros tipada implicitamente, os tipos dos parâmetros são inferidos do contexto em que a função anônima ocorre — especificamente, quando a função anônima é convertida para um tipo de delegate compatível ou tipo de árvore de expressões, esse tipo fornece os tipos de parâmetro (§10.7).
Em lambda_expression com um único parâmetro tipado implicitamente, pode-se omitir os parênteses da lista de parâmetros. Em outras palavras, uma função anônima do formato
( «param» ) => «expr»
pode ser abreviado para
«param» => «expr»
A lista de parâmetros de uma função anônima no formato de anonymous_method_expression é opcional. Se fornecidos, os parâmetros devem ser explicitamente tipados. Caso contrário, a função anônima será conversível para um delegado com qualquer lista de parâmetros que não contenha parâmetros de saída.
O corpo do bloco de uma função anônima é sempre acessível (§13.2).
Exemplo: alguns exemplos de funções anônimas seguem abaixo:
x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, block body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, block body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters async (t1,t2) => await t1 + await t2 // Async delegate (int x) { return x + 1; } // Anonymous method expression delegate { return 1 + 1; } // Parameter list omitted
fim do exemplo
O comportamento das expressões lambda e das expressões de método anônimo é o mesmo, exceto nos seguintes pontos:
- anonymous_method_expressions permitem que a lista de parâmetros seja totalmente omitida, facilitando a conversibilidade para tipos de delegados de qualquer lista de parâmetros de valor.
- lambda_expressions permite que os tipos de parâmetro sejam omitidos e inferidos, enquanto anonymous_method_expressions exige que os tipos de parâmetro sejam explicitamente declarados.
- O corpo de uma lambda_expression pode ser uma expressão ou um bloco, enquanto o corpo de uma anonymous_method_expression precisa ser um bloco.
- Somente lambda_expressions têm conversões para tipos de árvore de expressão compatíveis (§8.6).
12.19.2 Assinaturas de função anônimas
anonymous_function_signature de uma função anônima define os nomes e, opcionalmente, os tipos dos parâmetros para a função anônima. O escopo dos parâmetros da função anônima é anonymous_function_body (§7.7). Junto com a lista de parâmetros (se fornecida) o corpo do método anônimo constitui um espaço de declaração (§7.3). Portanto, é um erro de tempo de compilação se o nome de um parâmetro da função anônima for igual ao nome de uma variável local, constante local ou parâmetro cujo escopo inclui o anonymous_method_expression ou lambda_expression.
Se uma função anônima tiver um explicit_anonymous_function_signature, então o conjunto dos tipos de delegados compatíveis e dos tipos de árvores de expressão será restrito àqueles que possuam os mesmos tipos de parâmetros e modificadores na mesma ordem (§10.7). Ao contrário das conversões de grupo de métodos (§10.8), não há suporte para contravariância de tipos de parâmetro de função anônima. Se uma função anônima não tiver anonymous_function_signature, o conjunto de tipos de delegado e tipos de árvore de expressão compatíveis será restrito àqueles que não têm parâmetros de saída.
Observe que anonymous_function_signature não pode incluir atributos ou uma matriz de parâmetros. No entanto, anonymous_function_signature pode ser compatível com um tipo de delegado cuja lista de parâmetros contém uma matriz de parâmetros.
Observe também que a conversão em um tipo de árvore de expressão, mesmo se compatível, ainda pode falhar em tempo de compilação (§8.6).
12.19.3 Corpos de função anônima
O corpo (expressão ou bloco) de uma função anônima está sujeito às seguintes regras:
- Se a função anônima incluir uma assinatura, os parâmetros especificados na assinatura estarão disponíveis no corpo. Se a função anônima não tiver assinatura, ela poderá ser convertida em um tipo de delegado ou tipo de expressão com parâmetros (§10.7), mas os parâmetros não poderão ser acessados no corpo.
- Com exceção dos parâmetros por referência especificados na assinatura (se houver) da função anônima delimitante mais próxima, é um erro de tempo de compilação para o corpo acessar um parâmetro por referência.
- Com exceção dos parâmetros especificados na assinatura (se houver) da função anônima delimitante mais próxima, é um erro de compilação se o corpo acessar um parâmetro de tipo
ref struct
. - Quando o tipo de
this
é um tipo de estrutura, é um erro de tempo de compilação que o corpo acessethis
. Isso é verdade se o acesso for explícito (como emthis.x
) ou implícito (como emx
em quex
é um membro de instância do struct). Essa regra simplesmente proíbe esse acesso e não afeta se a pesquisa de membro resultar em um membro do struct. - O corpo tem acesso às variáveis externas (§12.19.6) da função anônima. O acesso a uma variável externa referenciará a instância da variável que está ativa no momento da avaliação da lambda_expression ou anonymous_method_expression (§12.19.7).
- É um erro de tempo de compilação se o corpo contiver uma instrução
goto
, uma instruçãobreak
ou uma instruçãocontinue
cujo destino está fora do corpo ou dentro do corpo de uma função anônima contida. - Uma instrução
return
no corpo retorna o controle de uma invocação da função anônima delimitante mais próxima, não do membro da função que a envolve.
Não está claro explicitamente se existe outro meio de executar o bloco de uma função anônima além da avaliação e da invocação de lambda_expression ou anonymous_method_expression. Em particular, um compilador pode optar por implementar uma função anônima sintetizando um ou mais tipos ou métodos nomeados. Os nomes de tais elementos sintetizados devem ser de um formato reservado para uso do compilador (§6.4.3).
12.19.4 Resolução de sobrecarga
Funções anônimas em uma lista de argumentos participam da inferência do tipo e da resolução de sobrecarga. Consulte §12.6.3 e §12.6.4 para obter as regras exatas.
Exemplo: o exemplo a seguir ilustra o efeito das funções anônimas na resolução de sobrecarga.
class ItemList<T> : List<T> { public int Sum(Func<T, int> selector) { int sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } public double Sum(Func<T, double> selector) { double sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } }
A classe
ItemList<T>
tem dois métodosSum
. Cada um usa um argumentoselector
, que extrai o valor a ser somado de um elemento da lista. O valor extraído pode ser umint
ou umdouble
, e a soma resultante também é umint
ou umdouble
.Os métodos
Sum
poderiam, por exemplo, ser usados para calcular somas de uma lista de linhas de detalhe em um pedido.class Detail { public int UnitCount; public double UnitPrice; ... } class A { void ComputeSums() { ItemList<Detail> orderDetails = GetOrderDetails( ... ); int totalUnits = orderDetails.Sum(d => d.UnitCount); double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount); ... } ItemList<Detail> GetOrderDetails( ... ) { ... } }
Na primeira invocação de
orderDetails.Sum
, os dois métodosSum
são aplicáveis porque a função anônimad => d.UnitCount
é compatível comFunc<Detail,int>
eFunc<Detail,double>
. No entanto, a resolução de sobrecarga escolhe o primeiro métodoSum
porque a conversão paraFunc<Detail,int>
é melhor do que a conversão paraFunc<Detail,double>
.Na segunda invocação de
orderDetails.Sum
, somente o segundo métodoSum
é aplicável porque a função anônimad => d.UnitPrice * d.UnitCount
produz um valor do tipodouble
. Assim, a resolução de sobrecarga escolhe o segundo métodoSum
para essa invocação.fim do exemplo
12.19.5 Funções anônimas e associação dinâmica
Uma função anônima não pode ser um receptor, argumento ou operando de uma operação associada dinamicamente.
12.19.6 Variáveis externas
12.19.6.1 Geral
Qualquer matriz de parâmetro, parâmetro de valor ou variável local cujo escopo inclui lambda_expression ou anonymous_method_expression é chamada de variável externa da função anônima. Em um membro de função de instância de uma classe, esse valor é considerado um parâmetro de valor e é uma variável externa de qualquer função anônima contida no membro da função.
12.19.6.2 Variáveis externas capturadas
Quando uma variável externa é referenciada por uma função anônima, diz-se que a variável externa foi capturada pela função anônima. Normalmente, o tempo de vida de uma variável local é limitado à execução do bloco ou instrução ao qual está associado (§9.2.9.1). No entanto, o tempo de vida de uma variável externa capturada é estendido pelo menos até que a árvore de delegado ou expressão criada a partir da função anônima se torne elegível para coleta de lixo.
Exemplo: no exemplo
delegate int D(); class Test { static D F() { int x = 0; D result = () => ++x; return result; } static void Main() { D d = F(); Console.WriteLine(d()); Console.WriteLine(d()); Console.WriteLine(d()); } }
a variável local
x
é capturada pela função anônima e o tempo de vida dex
é estendido pelo menos até que o delegado retornado deF
se torne qualificado para coleta de lixo. Como cada invocação da função anônima opera na mesma instância dex
, a saída do exemplo é:1 2 3
fim do exemplo
Quando uma variável local ou um parâmetro de valor é capturado por uma função anônima, a variável local ou parâmetro não é mais considerado uma variável fixa (§23.4), mas é considerada uma variável movível. No entanto, variáveis externas capturadas não podem ser usadas em uma instrução fixed
(§23.7), portanto, o endereço de uma variável externa capturada não pode ser tomado.
Observação: Ao contrário de uma variável não capturada, uma variável local capturada pode ser exposta simultaneamente a vários threads de execução. fim da observação
12.19.6.3 Instanciação de variáveis locais
Uma variável local é considerada instanciada quando a execução entra no escopo da variável.
Exemplo: por exemplo, quando o método a seguir é invocado, a variável local
x
é instanciada e inicializada três vezes – uma vez para cada iteração do loop.static void F() { for (int i = 0; i < 3; i++) { int x = i * 2 + 1; ... } }
No entanto, mover a declaração de
x
fora do loop resulta em uma única instanciação dex
:static void F() { int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; ... } }
fim do exemplo
Quando não é capturada, não há como observar exatamente a frequência com que uma variável local é instanciada, pois os tempos de vida das instanciações são desarticulados, é possível que cada instanciação simplesmente use o mesmo local de armazenamento. No entanto, quando uma função anônima captura uma variável local, os efeitos da instanciação se tornam aparentes.
Exemplo: o exemplo
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { int x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
produz a saída:
1 3 5
No entanto, quando a declaração de
x
é movida para fora do loop:delegate void D(); class Test { static D[] F() { D[] result = new D[3]; int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
a saída é:
5 5 5
Observe que um compilador é permitido (mas não necessário) para otimizar as três instanciações em uma única instância de delegado (§10.7.2).
fim do exemplo
Se for-loop declarar uma variável de iteração, essa variável em si será considerada declarada fora do loop.
Exemplo: portanto, se o exemplo for alterado para capturar a variável de iteração em si:
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { result[i] = () => Console.WriteLine(i); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
apenas uma instância da variável de iteração será capturada, o que produzirá a saída:
3 3 3
fim do exemplo
É possível que delegados de funções anônimas compartilhem algumas variáveis capturadas, mas tenham instâncias separadas de outras.
Exemplo: por exemplo, se
F
for alterado parastatic D[] F() { D[] result = new D[3]; int x = 0; for (int i = 0; i < 3; i++) { int y = 0; result[i] = () => Console.WriteLine($"{++x} {++y}"); } return result; }
os três delegados capturam a mesma instância de
x
, mas capturam instâncias separadas dey
, e a saída é:1 1 2 1 3 1
fim do exemplo
Funções anônimas separadas podem capturar a mesma instância de uma variável externa.
Exemplo: no exemplo:
delegate void Setter(int value); delegate int Getter(); class Test { static void Main() { int x = 0; Setter s = (int value) => x = value; Getter g = () => x; s(5); Console.WriteLine(g()); s(10); Console.WriteLine(g()); } }
as duas funções anônimas capturam a mesma instância da variável local
x
e, portanto, podem "se comunicar" por meio dessa variável. A saída de exemplo é:5 10
fim do exemplo
12.19.7 Avaliação de expressões de função anônima
Uma função anônima F
sempre deve ser convertida em um tipo de delegado D
ou em um tipo de árvore de expressão E
, diretamente ou por meio da execução de uma expressão de criação de delegado new D(F)
. Essa conversão determina o resultado da função anônima, conforme descrito em §10.7.
12.19.8 Exemplo de implementação
Essa subcláusula é informativa.
Esse subcláusula descreve uma possível implementação de conversões de função anônima em termos de outros constructos de C#. A implementação descrita aqui baseia-se nos mesmos princípios usados por um compilador de C# comercial, mas não é de forma alguma uma implementação obrigatória, nem é a única possível. Ela menciona apenas brevemente as conversões em árvores de expressão, pois sua semântica exata está fora do escopo dessa especificação.
O restante dessa subcláusula fornece vários exemplos de código que contém funções anônimas com características diferentes. Para cada exemplo, fornece-se uma tradução correspondente para o código que usa apenas outros constructos de C#. Nos exemplos, assume-se o identificador D
por representar o seguinte tipo de delegado:
public delegate void D();
A forma mais simples de uma função anônima é aquela que não captura nenhuma variável externa:
delegate void D();
class Test
{
static void F()
{
D d = () => Console.WriteLine("test");
}
}
Isso pode ser convertido em uma instanciação de delegado que faz referência a um método estático gerado pelo compilador no qual o código da função anônima é colocado:
delegate void D();
class Test
{
static void F()
{
D d = new D(__Method1);
}
static void __Method1()
{
Console.WriteLine("test");
}
}
No exemplo abaixo, uma função anônima faz referência aos membros de instância de this
:
delegate void D();
class Test
{
int x;
void F()
{
D d = () => Console.WriteLine(x);
}
}
Isso pode ser traduzido para um método de instância gerado pelo compilador que contém o código da função anônima:
delegate void D();
class Test
{
int x;
void F()
{
D d = new D(__Method1);
}
void __Method1()
{
Console.WriteLine(x);
}
}
Neste exemplo, a função anônima captura uma variável local:
delegate void D();
class Test
{
void F()
{
int y = 123;
D d = () => Console.WriteLine(y);
}
}
O tempo de vida da variável local agora deve ser estendido para ser igual a pelo menos o tempo de vida de delegado da função anônima. Isso pode ser obtido elevando a variável local a um campo de uma classe gerada pelo compilador. A instanciação da variável local (§12.19.6.3) corresponde à criação de uma instância da classe gerada pelo compilador e o acesso à variável local corresponde ao acesso a um campo na instância da classe gerada pelo compilador. Além disso, a função anônima torna-se um método de instância da classe gerada pelo compilador:
delegate void D();
class Test
{
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1()
{
Console.WriteLine(y);
}
}
}
Finalmente, a função anônima a seguir captura this
bem como duas variáveis locais com tempos de vida diferentes:
delegate void D();
class Test
{
int x;
void F()
{
int y = 123;
for (int i = 0; i < 10; i++)
{
int z = i * 2;
D d = () => Console.WriteLine(x + y + z);
}
}
}
Aqui, uma classe gerada pelo compilador é criada para cada bloco em que as variáveis locais são capturadas, permitindo que as variáveis em diferentes blocos tenham tempos de vida independentes. Uma instância de __Locals2
, a classe gerada pelo compilador para o bloco interno, contém a variável local z
e um campo que faz referência a uma instância de __Locals1
. Uma instância de __Locals1
, a classe gerada pelo compilador para o bloco externo, contém a variável local y
e um campo que faz referência this
do membro da função delimitador. Com essas estruturas de dados, é possível alcançar todas as variáveis externas capturadas por meio de uma instância de __Local2
e, portanto, o código da função anônima pode ser implementado como um método de instância dessa classe.
delegate void D();
class Test
{
int x;
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++)
{
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1()
{
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
A mesma técnica aplicada aqui para capturar as variáveis locais também pode ser usada ao converter-se funções anônimas em árvores de expressão: as referências aos objetos gerados pelo compilador podem ser armazenadas na árvore de expressão, e o acesso às variáveis locais pode ser representado como acessos de campo nesses objetos. A vantagem dessa abordagem é que ela permite que as variáveis locais "de precisão" sejam compartilhadas entre os delegados e as árvores de expressão.
Fim do texto informativo.
12.20 Expressões de consulta
12.20.1 Geral
As expressões de consulta fornecem uma sintaxe integrada à linguagem para consultas semelhantes a linguagens de consulta relacionais e hierárquicas, como SQL e XQuery.
query_expression
: from_clause query_body
;
from_clause
: 'from' type? identifier 'in' expression
;
query_body
: query_body_clause* select_or_group_clause query_continuation?
;
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
;
let_clause
: 'let' identifier '=' expression
;
where_clause
: 'where' boolean_expression
;
join_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression
;
join_into_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression 'into' identifier
;
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
select_or_group_clause
: select_clause
| group_clause
;
select_clause
: 'select' expression
;
group_clause
: 'group' expression 'by' expression
;
query_continuation
: 'into' identifier query_body
;
Uma expressão de consulta começa com uma cláusula from
e termina com uma cláusula select
ou group
. A cláusula from
inicial pode ser seguida por zero ou mais cláusulas from
, let
, where
, join
ou orderby
. Cada cláusula from
é um gerador que introduz uma variável de intervalo que varia entre os elementos de uma sequência de. Cada cláusula let
introduz uma variável de intervalo que representa um valor computado por meio de variáveis de intervalo anteriores. Cada cláusula where
é um filtro que exclui os itens do resultado. Cada cláusula join
compara as chaves especificadas da sequência de origem com as chaves de outra sequência, gerando os pares correspondentes. Cada cláusula orderby
reordena os itens de acordo com os critérios especificados. A cláusula select
ou group
final especifica a forma do resultado em termos das variáveis de intervalo. Finalmente, uma cláusula into
pode ser usada para "unir" consultas tratando os resultados de uma consulta como um gerador em uma consulta subsequente.
12.20.2 Ambiguidades em expressões de consulta
As expressões de consulta usam várias palavras-chave contextuais (§6.4.4): ascending
, by
, descending
, equals
, from
, group
, into
, join
, let
, on
, orderby
, select
e where
.
Para evitar ambiguidades que possam surgir do uso desses identificadores como palavras-chave e nomes simples, os identificadores são considerados palavras-chave em qualquer lugar dentro de uma expressão de consulta, a menos que sejam prefixados com "@
" (§6.4.4) nesse caso, eles são considerados identificadores. Para essa finalidade, uma expressão de consulta é qualquer expressão que comece com "from
identificador" seguido por qualquer token, exceto ";
", "=
" ou ",
".
12.20.3 Conversão de expressão de consulta
12.20.3.1 Geral
A linguagem C# não especifica a semântica de execução das expressões de consulta. Em vez disso, as expressões de consulta são convertidas em invocações de métodos que aderem ao padrão de expressão de consulta (§12.20.4). Especificamente, as expressões de consulta são convertidas em invocações de métodos chamados Where
, Select
, SelectMany
, Join
, GroupJoin
, OrderBy
, OrderByDescending
, ThenBy
, ThenByDescending
, GroupBy
e Cast
. Espera-se que esses métodos tenham tipos de retorno e assinaturas específicos, conforme descrito em §12.20.4. Esses métodos podem ser métodos de instância do objeto que está sendo consultado ou métodos de extensão que são externos ao objeto. Esses métodos implementam a execução real da consulta.
A tradução de expressões de consulta para invocações de método é um mapeamento sintático que ocorre antes da execução de qualquer associação de tipo ou resolução de sobrecarga. Após a conversão das expressões de consulta, as invocações de método resultantes são processadas como invocações de método regular; isso pode, por sua vez, descobrir erros de tempo de compilação. Essas condições de erro incluem, mas não se limitam a métodos que não existem, argumentos dos tipos errados e métodos genéricos nos quais ocorre falha da inferência de tipo.
Uma expressão de consulta é processada aplicando-se repetidamente as conversões a seguir até que nenhuma redução adicional seja possível. As conversões são listadas em ordem de aplicação: cada seção pressupõe que as conversões nas seções anteriores foram executadas exaustivamente e, depois de esgotadas, uma seção não será revisitada posteriormente no processamento da mesma expressão de consulta.
É um erro de compilação quando uma expressão de consulta inclui uma atribuição a uma variável de intervalo ou o uso de uma variável de intervalo como argumento para um parâmetro de referência ou de saída.
Algumas conversões injetam variáveis de intervalo com identificadores transparentes indicados por *. Eles são descritos mais adiante em §12.20.3.8.
12.20.3.2 Expressões de consulta com continuações
Uma expressão de consulta com uma continuação seguindo o corpo de consulta
from «x1» in «e1» «b1» into «x2» «b2»
é convertida em
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
As conversões nas seções a seguir pressupõem que as consultas não têm continuações.
Exemplo: O Exemplo:
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }
é convertida em:
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }
a tradução final disso é:
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
fim do exemplo
12.20.3.3 Tipos de variável de intervalo explícito
Uma cláusula from
que especifica explicitamente um tipo de variável de intervalo
from «T» «x» in «e»
é convertida em
from «x» in ( «e» ) . Cast < «T» > ( )
Uma cláusula join
que especifica explicitamente um tipo de variável de intervalo
join «T» «x» in «e» on «k1» equals «k2»
é convertida em
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
As conversões nas seções a seguir pressupõem que as consultas não têm tipos de variáveis de intervalo explícitos.
Exemplo: o exemplo
from Customer c in customers where c.City == "London" select c
é convertida em
from c in (customers).Cast<Customer>() where c.City == "London" select c
cuja tradução final é
customers. Cast<Customer>(). Where(c => c.City == "London")
fim do exemplo
Observação: tipos de variável de intervalo explícito são úteis para consultar coleções que implementam a interface de
IEnumerable
não genérica, mas não a interface deIEnumerable<T>
genérica. No exemplo acima, esse seria o caso se os clientes fossem do tipoArrayList
. fim da observação
12.20.3.4 Expressões de consulta degeneradas
Uma expressão de consulta do formato
from «x» in «e» select «x»
é convertida em
( «e» ) . Select ( «x» => «x» )
Exemplo: o exemplo
from c in customers select c
é convertida em
(customers).Select(c => c)
fim do exemplo
Uma expressão de consulta degenerada é aquela que seleciona trivialmente os elementos da origem.
Observação: fases posteriores da conversão (§12.20.3.6 e §12.20.3.7) removem consultas degeneradas introduzidas por outras etapas de tradução substituindo-as por sua origem. No entanto, é importante garantir que o resultado de uma expressão de consulta nunca seja o próprio objeto de origem. Caso contrário, retornar o resultado de tal consulta pode expor inadvertidamente dados privados (por exemplo, uma matriz de elementos) a um chamador. Portanto, essa etapa protege as consultas degeneradas escritas diretamente no código-fonte chamando explicitamente
Select
na origem. Em seguida, cabe aos implementadores deSelect
e outros operadores de consulta garantir que esses métodos nunca retornem o próprio objeto de origem. fim da observação
12.20.3.5 Cláusulas from, let, where, join e orderby
Uma expressão de consulta com uma segunda cláusula from
seguida por uma cláusula select
from «x1» in «e1»
from «x2» in «e2»
select «v»
é convertida em
( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )
Exemplo: o exemplo
from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.Total }
é convertida em
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )
fim do exemplo
Uma expressão de consulta com uma segunda cláusula from
seguida por um corpo de consulta Q
contendo um conjunto não vazio de cláusulas do corpo da consulta:
from «x1» in «e1»
from «x2» in «e2»
Q
é convertida em
from * in («e1») . SelectMany( «x1» => «e2» ,
( «x1» , «x2» ) => new { «x1» , «x2» } )
Q
Exemplo: o exemplo
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
é convertida em
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
cuja tradução final é
customers. SelectMany(c => c.Orders, (c,o) => new { c, o }). OrderByDescending(x => x.o.Total). Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
onde
x
é um identificador gerado pelo compilador que, caso contrário, é invisível e inacessível.fim do exemplo
Uma expressão let
com a cláusula from
que a precede:
from «x» in «e»
let «y» = «f»
...
é convertida em
from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )
...
Exemplo: o exemplo
from o in orders let t = o.Details.Sum(d => d.UnitPrice * d.Quantity) where t >= 1000 select new { o.OrderID, Total = t }
é convertida em
from * in (orders).Select( o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) where t >= 1000 select new { o.OrderID, Total = t }
cuja tradução final é
orders .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) .Where(x => x.t >= 1000) .Select(x => new { x.o.OrderID, Total = x.t })
onde
x
é um identificador gerado pelo compilador que, caso contrário, é invisível e inacessível.fim do exemplo
Uma expressão where
com a cláusula from
que a precede:
from «x» in «e»
where «f»
...
é convertida em
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Uma cláusula join
imediatamente seguida por uma cláusula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
é convertida em
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Exemplo: o exemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }
é convertida em
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })
fim do exemplo
Uma cláusula join
seguida por uma cláusula do corpo da consulta:
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
é convertida em
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Uma cláusula join
-into
imediatamente seguida por uma cláusula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
é convertida em
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
Uma cláusula join into
seguida por uma cláusula do corpo da consulta
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into *g»
...
é convertida em
from * in ( «e1» ) . GroupJoin(
«e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...
Exemplo: o exemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
é convertida em
from * in (customers).GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
cuja tradução final é
customers .GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) .Select(x => new { x, n = x.co.Count() }) .Where(y => y.n >= 10) .Select(y => new { y.x.c.Name, OrderCount = y.n })
em que
x
ey
são identificadores gerados pelo compilador que são invisíveis e inacessíveis.fim do exemplo
Uma cláusula orderby
e a cláusula from
que a precede:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
é convertida em
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Se uma cláusula ordering
especificar um indicador de direção decrescente, uma invocação de OrderByDescending
ou ThenByDescending
será produzida.
Exemplo: o exemplo
from o in orders orderby o.Customer.Name, o.Total descending select o
tem a tradução final
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)
fim do exemplo
As conversões a seguir pressupõem que não há cláusulas let
, where
, join
ou orderby
e não mais do que uma cláusula from
inicial em cada expressão de consulta.
12.20.3.6 Cláusulas Select
Uma expressão de consulta do formato
from «x» in «e» select «v»
é convertida em
( «e» ) . Select ( «x» => «v» )
exceto quando «v»
é o identificador «x»
, a conversão é apenas
( «e» )
Exemplo: o exemplo
from c in customers.Where(c => c.City == "London") select c
é simplesmente convertida em
(customers).Where(c => c.City == "London")
fim do exemplo
12.20.3.7 Agrupar cláusulas
Uma cláusula group
from «x» in «e» group «v» by «k»
é convertida em
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
exceto quando «v»
é o identificador «x»
, a conversão é
( «e» ) . GroupBy ( «x» => «k» )
Exemplo: o exemplo
from c in customers group c.Name by c.Country
é convertida em
(customers).GroupBy(c => c.Country, c => c.Name)
fim do exemplo
12.20.3.8 Identificadores transparentes
Determinadas traduções injetam variáveis de alcance com identificadores transparentes indicados por *
. Identificadores transparentes existem apenas como uma etapa intermediária no processo de conversão da expressão de consulta.
Quando uma conversão de consulta injeta um identificador transparente, outras etapas de conversão propagam o identificador transparente em funções anônimas e inicializadores de objetos anônimos. Nesses contextos, identificadores transparentes têm o seguinte comportamento:
- Quando um identificador transparente ocorre como um parâmetro em uma função anônima, os membros do tipo anônimo associado estão automaticamente dentro do escopo do corpo da função anônima.
- Quando um membro com um identificador transparente está no escopo, os membros desse membro também estão no escopo.
- Quando um identificador transparente ocorre como um declarador de membro em um inicializador de objeto anônimo, ele introduz um membro com um identificador transparente.
Nas etapas de conversão descritas acima, os identificadores transparentes são sempre introduzidos junto com os tipos anônimos, com a intenção de capturar várias variáveis de intervalo como membros de um único objeto. Uma implementação de C# tem permissão para usar um mecanismo diferente dos tipos anônimos a fim de agrupar diversas variáveis de intervalo. Os exemplos de conversão a seguir pressupõem que os tipos anônimos são usados e mostram uma possível conversão de identificadores transparentes.
Exemplo: o exemplo
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.Total }
é convertida em
from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.Total }
que é convertida ainda mais em
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })
que, quando identificadores transparentes são apagados, é equivalente a
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(x => x.o.Total) .Select(x => new { x.c.Name, x.o.Total })
onde
x
é um identificador gerado pelo compilador que, caso contrário, é invisível e inacessível.O exemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
é convertida em
from * in (customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
que é ainda mais reduzida a
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }) .Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { c.Name, o.OrderDate, p.ProductName })
cuja tradução final é
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d }) .Join(products, y => y.d.ProductID, p => p.ProductID, (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })
em que
x
ey
são identificadores gerados pelo compilador que são invisíveis e inacessíveis. fim do exemplo
12.20.4 O padrão da expressão de consulta
O padrão da expressão de consulta estabelece um padrão de métodos que os tipos podem implementar para dar suporte às expressões de consulta.
Um tipo genérico C<T>
dá suporte ao padrão da expressão de consulta se seus métodos de membro público e os métodos de extensão publicamente acessíveis puderem ser substituídos pela definição de classe a seguir. Os membros e os métodos de extensão acessíveis são referidos como o "formato" de um tipo genérico C<T>
. Um tipo genérico é usado para ilustrar as relações adequadas entre os tipos de parâmetro e de retorno, mas também é possível implementar o padrão para os tipos não genéricos.
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>() { ... }
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate) { ... }
public C<U> Select<U>(Func<T,U> selector) { ... }
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector) { ... }
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector) { ... }
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}
class G<K,T> : C<T>
{
public K Key { get; }
}
Os métodos acima usam os tipos delegados genéricos Func<T1, R>
e Func<T1, T2, R>
, mas também podem ter usado outros tipos de delegados ou árvore de expressão com as mesmas relações nos tipos de parâmetro e retorno.
Observação: a relação recomendada entre
C<T>
eO<T>
que garante que os métodosThenBy
eThenByDescending
estejam disponíveis apenas no resultado de umOrderBy
ouOrderByDescending
. fim da observação
Observação: a forma recomendada do resultado de
GroupBy
– uma sequência de sequências, em que cada sequência interna tem uma propriedadeKey
adicional. fim da observação
Observação: como as expressões de consulta são convertidas para invocações de método por meio de um mapeamento sintático, os tipos têm flexibilidade considerável na forma como implementam um ou todos os padrões da expressão de consulta. Por exemplo, os métodos do padrão podem ser implementados como métodos de instância ou como métodos de extensão porque os dois têm a mesma sintaxe de invocação e os métodos podem solicitar delegado ou árvores de expressão porque as funções anônimas são conversíveis para os dois. Tipos que implementam apenas parte do padrão de expressão de consulta suportam apenas conversões de expressões de consulta que são mapeadas para os métodos compatíveis com esse tipo. fim da observação
Observação: o namespace
System.Linq
fornece uma implementação do padrão da expressão de consulta para qualquer tipo que implemente a interfaceSystem.Collections.Generic.IEnumerable<T>
. fim da observação
12.21 Operadores de atribuição
12.21.1 Geral
Todos, exceto um dos operadores de atribuição, atribuem um novo valor a uma variável, propriedade, evento ou elemento indexador. A exceção = ref
atribui uma referência variável (§9.5) a uma variável de referência (§9.7).
assignment
: unary_expression assignment_operator expression
;
assignment_operator
: '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '??='
| right_shift_assignment
;
O operando esquerdo de uma atribuição deve ser uma expressão classificada como uma variável ou, com exceção de = ref
, um acesso de propriedade, um acesso de indexador, um acesso de evento ou uma tupla. Uma expressão de declaração não é permitida diretamente como um operando esquerdo, mas pode ocorrer como uma etapa na avaliação de uma atribuição de desconstrução.
O operador =
é chamado de operador de atribuição simples. Ele atribui o valor ou os valores do operando direito à variável, propriedade, elemento indexador ou aos elementos de tupla fornecidos pelo operando esquerdo. O operando esquerdo do operador de atribuição simples não deve ser um acesso de evento (exceto conforme descrito em §15.8.2). O operador de atribuição simples é descrito em §12.21.2.
O operador = ref
é chamado de operador de atribuição ref. Constitui o operando à direita, que deve ser variable_reference (§9.5), o referenciante da variável de referência designada pelo operando esquerdo. O operador de atribuição ref é descrito em §12.21.3.
Os operadores de atribuição, exceto os operadores =
e = ref
, são chamados de operadores de atribuição composta. Esses operadores são processados da seguinte maneira:
- Para o operador
??=
, somente se o valor do operando esquerdo fornull
, o operando à direita será avaliado e o resultado atribuído à variável, propriedade ou elemento de indexador especificado pelo operando esquerdo. - Caso contrário, a operação indicada é executada nos dois operandos e, em seguida, o valor resultante é atribuído ao elemento variável, propriedade ou indexador fornecido pelo operando esquerdo. Os operadores de atribuição composta são descritos em §12.21.4.
Os operadores +=
e -=
com uma expressão de acesso de evento como operando esquerdo são chamados de operadores de atribuição de eventos. Nenhum outro operador de atribuição é válido com um acesso de evento como o operando esquerdo. Os operadores de atribuição de evento são descritos em §12.21.5.
Os operadores de atribuição são associativos à direita, o que significa que as operações são agrupadas da direita para a esquerda.
Exemplo: uma expressão do formato
a = b = c
é avaliada comoa = (b = c)
. fim do exemplo
12.21.2 Atribuição simples
O operador =
é chamado de operador de atribuição simples.
Se o operando esquerdo de uma atribuição simples for do formato E.P
ou E[Ei]
em que E
tem o tipo de tempo de compilação dynamic
, a atribuição será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão de atribuição é dynamic
e a resolução descrita abaixo ocorrerá em run-time com base no tipo de run-time de E
. Se o operando esquerdo for da forma E[Ei]
em que pelo menos um elemento de Ei
tiver o tipo de tempo de compilação dynamic
, e o tipo de tempo de compilação de E
não for uma matriz, o acesso ao indexador resultante é dinamicamente vinculado, mas com verificação de tempo de compilação limitada (§12.6.5).
Uma atribuição simples em que o operando esquerdo é classificado como uma tupla também é chamada de atribuição de desconstrução. Se algum dos elementos da tupla do operando esquerdo tiver um nome, ocorrerá um erro em tempo de compilação. Se qualquer um dos elementos da tupla do operando esquerdo for uma declaration_expression, e qualquer outro elemento não for uma declaration_expression ou um simples descarte, ocorrerá um erro em tempo de compilação.
O tipo de uma atribuição simples x = y
é o tipo de uma atribuição para x
de y
, que é determinada recursivamente da seguinte maneira:
- Se
x
for uma expressão de tupla(x1, ..., xn)
ey
puder ser desconstruída em uma expressão de tupla(y1, ..., yn)
com elementos den
(§12.7), e cada atribuição paraxi
deyi
tiver o tipoTi
, a atribuição terá o tipo(T1, ..., Tn)
. - Caso contrário, se
x
for classificado como uma variável, a variável não seráreadonly
,x
tiver um tipoT
ey
tiver uma conversão implícita paraT
, a atribuição terá o tipoT
. - Caso contrário, se
x
for classificado como uma variável tipada implicitamente (ou seja, uma expressão de declaração tipada implicitamente) ey
tiver um tipoT
, o tipo inferido da variável seráT
e a atribuição terá o tipoT
. - Caso contrário, se
x
for classificado como um acesso de indexador ou propriedade, a propriedade ou o indexador tiver um acessador set acessível,x
tiver um tipoT
ey
tiver uma conversão implícita paraT
, a atribuição terá o tipoT
. - Caso contrário, a atribuição não será válida e ocorrerá um erro de tempo de associação.
O processamento run-time de uma atribuição simples do formato x = y
com o tipo T
é executado como uma atribuição para x
de y
com o tipo T
, que consiste nas seguintes etapas recursivas:
-
x
é avaliado caso ainda não tenha sido. - Se
x
for classificado como uma variável,y
será avaliado e, se necessário, convertido emT
por meio de uma conversão implícita (§10.2).- Se a variável fornecida por
x
for um elemento de matriz de reference_type, uma verificação run-time será executada para garantir que o valor computado paray
seja compatível com a instância de matriz da qualx
é um elemento. A verificação terá êxito sey
fornull
ou se uma conversão de referência implícita (§10.2.8) existir no tipo da instância referenciada pory
para o tipo de elemento real da instância de matriz que contémx
. Caso contrário, umaSystem.ArrayTypeMismatchException
será gerada. - O valor resultante da avaliação e conversão de
y
é armazenado no local fornecido pela avaliação dex
e é gerado como resultado da atribuição.
- Se a variável fornecida por
- Se
x
for classificado como uma propriedade ou acesso de indexador:-
y
é avaliado e, se necessário, convertido emT
por meio de uma conversão implícita (§10.2). - O acessador set de
x
é invocado com o valor resultante da avaliação e conversão dey
como seu argumento de valor. - O valor resultante da avaliação e conversão de
y
é produzido como resultado da atribuição.
-
- Se
x
for classificado como uma tupla(x1, ..., xn)
com aridaden
:-
y
é desconstruído com elementosn
em uma expressão de tuplae
. - uma tupla de resultado
t
é criada ao convertere
emT
usando uma conversão de tupla implícita. - para cada
xi
da esquerda para a direita, uma atribuição axi
det.Itemi
é executada, exceto que osxi
não são avaliados novamente. -
t
é gerado como resultado da atribuição.
-
Observação: se o tipo de tempo de compilação de
x
fordynamic
e houver uma conversão implícita do tipo de tempo de compilação dey
paradynamic
, nenhuma resolução run-time será necessária. fim da observação
Observação: as regras de covariância de matriz (§17.6) permitem que um valor de um tipo de matriz
A[]
seja uma referência a uma instância de um tipo de matrizB[]
, desde que exista uma conversão de referência implícita deB
paraA
. Devido a essas regras, a atribuição a um elemento de matriz de um reference_type requer uma verificação run-time para garantir que o valor atribuído seja compatível com a instância da matriz. No exemplostring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchException
a última atribuição faz com que uma
System.ArrayTypeMismatchException
seja lançada porque uma referência a umArrayList
não pode ser armazenada em um elemento de umstring[]
.fim da observação
Quando uma propriedade ou indexador declarado em um struct_type é o destino de uma atribuição, a expressão de instância associada à propriedade ou ao acesso do indexador deve ser classificada como uma variável. Se a expressão de instância for classificada como um valor, ocorrerá um erro de tempo de associação.
Observação: devido a §12.8.7, a mesma regra também se aplica aos campos. fim da observação
Exemplo: dadas as declarações:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }
no exemplo
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;
as atribuições para
p.X
,p.Y
,r.A
er.B
são permitidas porquep
er
são variáveis. No entanto, no exemploRectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;
as atribuições são todas inválidas, já que
r.A
er.B
não são variáveis.fim do exemplo
12.21.3 Atribuição de referência
O operador = ref
é conhecido como operador de atribuição de referência.
O operando esquerdo deve ser uma expressão que se associe a uma variável de referência (§9.7), um parâmetro de referência (diferente de this
), um parâmetro de saída ou um parâmetro de entrada. O operando direito deve ser uma expressão que produz variable_reference designando um valor do mesmo tipo que o operando esquerdo.
É um erro de tempo de compilação se ref-safe-context (§9.7.2) do operando esquerdo for mais amplo do que ref-safe-context do operando à direita.
O operando direito deve ser definitivamente atribuído no ponto da atribuição de referência.
Quando o operando esquerdo se associa a um parâmetro de saída, ocorrerá um erro se esse parâmetro de saída não tiver sido definitivamente atribuído no início do operador de atribuição ref.
Se o operando esquerdo for um ref gravável (ou seja, ele designa qualquer coisa diferente de um parâmetro local ou de entrada ref readonly
), o operando à direita deverá ser um variable_reference gravável. Se a variável de operando direito for gravável, o operando esquerdo poderá ser uma referência gravável ou somente leitura.
A operação torna o operando esquerdo um alias da variável do operando direito. O alias pode ser definido como somente leitura, mesmo que a variável do operando direito seja gravável.
O operador de atribuição de referência resulta em uma variable_reference do tipo atribuído. Será gravável se o operando esquerdo for gravável.
O operador de atribuição de referência não deve ler o local de armazenamento referenciado pelo operando direito.
Exemplo: veja alguns exemplos de como usar
= ref
:public static int M1() { ... } public static ref int M2() { ... } public static ref uint M2u() { ... } public static ref readonly int M3() { ... } public static void Test() { int v = 42; ref int r1 = ref v; // OK, r1 refers to v, which has value 42 r1 = ref M1(); // Error; M1 returns a value, not a reference r1 = ref M2(); // OK; makes an alias r1 = ref M2u(); // Error; lhs and rhs have different types r1 = ref M3(); // error; M3 returns a ref readonly, which r1 cannot honor ref readonly int r2 = ref v; // OK; make readonly alias to ref r2 = ref M2(); // OK; makes an alias, adding read-only protection r2 = ref M3(); // OK; makes an alias and honors the read-only r2 = ref (r1 = ref M2()); // OK; r1 is an alias to a writable variable, // r2 is an alias (with read-only access) to the same variable }
fim do exemplo
Observação: ao ler-se o código usando um operador
= ref
, pode ser tentador ler a parteref
como sendo parte do operando. Isso pode causar alguma confusão quando o operando é uma expressão de?:
condicional. **Por exemplo, ao lerref int a = ref b ? ref x : ref y;
, é importante entender isso como= ref
sendo o operador eb ? ref x : ref y
sendo o operando à direita:ref int a = ref (b ? ref x : ref y);
. É importante ressaltar que a expressãoref b
não é parte dessa instrução, embora possa parecer assim à primeira vista. fim da observação
12.21.4 Atribuição composta
Se o operando esquerdo de uma atribuição composta for do formato E.P
ou E[Ei]
em que E
tem o tipo em tempo de compilação dynamic
, a atribuição será associada dinamicamente (§12.3.3). Nesse caso, o tipo de tempo de compilação da expressão de atribuição é dynamic
e a resolução descrita abaixo ocorrerá em run-time com base no tipo de run-time de E
. Se o operando esquerdo for da forma E[Ei]
em que pelo menos um elemento de Ei
tiver o tipo de tempo de compilação dynamic
, e o tipo de tempo de compilação de E
não for uma matriz, o acesso ao indexador resultante é dinamicamente vinculado, mas com verificação de tempo de compilação limitada (§12.6.5).
a ??= b
é equivalente a (T) (a ?? (a = b))
, exceto que a
é avaliado apenas uma vez, onde T
é o tipo de a
quando o tipo b
de é dinâmico e, caso contrário T
, é o tipo de a ?? b
.
Caso contrário, uma operação do formulário x «op»= y
é processada aplicando a resolução de sobrecarga do operador binário (§12.4.5) como se a operação tivesse sido gravada x «op» y
. Então
- Se o tipo de retorno do operador selecionado for implicitamente conversível para o tipo
x
, a operação será avaliada comox = x «op» y
, exceto quex
receberá a avaliação uma vez. - Caso contrário, se o operador selecionado for um operador predefinido, se o tipo de retorno do operador selecionado for explicitamente conversível para o tipo
x
e sey
for implicitamente conversível para o tipox
ou o operador for um operador de deslocamento, a operação será avaliada comox = (T)(x «op» y)
, em queT
é o tipox
, exceto quex
receberá a avaliação apenas uma vez. - Caso contrário, a atribuição composta é inválida e ocorre um erro de tempo de associação.
O termo "receberá a avaliação apenas uma vez" significa que, na avaliação de x «op» y
, os resultados das expressões constituintes de x
são temporariamente salvos e reutilizados ao executar-se a atribuição para x
.
Exemplo: no
A()[B()] += C()
de atribuição, em queA
é um método que retornaint[]
eB
eC
são métodos que retornamint
, os métodos são invocados apenas uma vez, na ordemA
,B
,C
. fim do exemplo
Quando o operando esquerdo de uma atribuição composta for um acesso de propriedade ou de indexador, a propriedade ou o indexador deverá ter tanto um acessador do tipo get quanto um acessador do tipo set. Se esse não for o caso, ocorrerá um erro de tempo de associação.
A segunda regra acima permite que x «op»= y
seja avaliado como x = (T)(x «op» y)
em contextos específicos. A regra existe para que os operadores pré-definidos possam ser usados como operadores compostos quando o operando esquerdo for do tipo sbyte
, byte
, short
, ushort
ou char
. Mesmo quando os dois argumentos são de um desses tipos, os operadores predefinidos produzem um resultado do tipo int
, conforme descrito em §12.4.7.3. Portanto, sem uma conversão, não seria possível atribuir o resultado ao operando esquerdo.
O efeito intuitivo da regra para operadores predefinidos é que x «op»= y
é permitido se x «op» y
e x = y
forem permitidos.
Exemplo: no código a seguir
byte b = 0; char ch = '\0'; int i = 0; b += 1; // OK b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // OK ch += 1; // Error, ch = 1 not permitted ch += (char)1; // OK
O motivo intuitivo para cada erro é que uma atribuição simples correspondente também teria sido um erro.
fim do exemplo
Observação: isso também significa que as operações de atribuição composta suportam operadores elevados. Como uma atribuição composta
x «op»= y
é avaliada comox = x «op» y
oux = (T)(x «op» y)
, as regras de avaliação abrangem implicitamente os operadores elevados. fim da observação
12.21.5 Atribuição de evento
Se o operando esquerdo do operador a += or -=
for classificado como um acesso de evento, a expressão será avaliada da seguinte maneira:
- A expressão de instância, se houver, do acesso ao evento é avaliada.
- O operando à direita do operador
+=
ou-=
é avaliado e, se necessário, convertido no tipo do operando esquerdo por meio de uma conversão implícita (§10.2). - Um acessador do evento é invocado, com uma lista de argumentos que consiste no valor calculado na etapa anterior. Se o operador for
+=
, o acessador de adição será invocado; se o operador for-=
, o acessador de remoção será invocado.
Uma expressão de atribuição de evento não suspende um valor. Portanto, uma expressão de atribuição de evento é válida apenas no contexto de um statement_expression (§13.7).
12.22 Expressão
Uma expressão é uma non_assignment_expression ou uma atribuição.
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.23 Expressões constantes
Uma expressão constante é uma expressão que deve ser totalmente avaliada em tempo de compilação.
constant_expression
: expression
;
Uma expressão constante deve ter o valor null
ou um dos seguintes tipos:
-
sbyte
,byte
,short
,ushort
,int
, ,uint
,long
, ,ulong
,char
,float
, ,double
,decimal
,bool
,string
; - um tipo de enumeração; ou
- uma expressão de valor padrão (§12.8.21) para um tipo de referência.
Somente os seguintes constructos são permitidos em expressões constantes:
- Literais (incluindo o literal
null
). - Referências a membros
const
de tipos de classe e estrutura. - Referências a membros de tipos de enumeração.
- Referências a constantes locais.
- Subexpressões entre parênteses, que são elas mesmas expressões constantes.
- Expressões de conversão.
- expressões
checked
eunchecked
. - Expressões
nameof
. - Os operadores unários predefinidos
+
,-
,!
(negação lógica) e~
. - Os operadores binários predefinidos
+
,-
,*
,/
,%
,<<
,>>
,&
,|
,^
,&&
,||
,==
,!=
,<
,>
,<=
e>=
. - O operador condicional
?:
. - Operador tolerante a valores nulos
!
(§12.8.9). - expressões
sizeof
, desde que o tipo não gerenciado seja um dos tipos especificados em §23.6.9 para os quaissizeof
retorna um valor constante. - Expressões de valor padrão, desde que o tipo seja um dos tipos listados anteriormente.
As seguintes conversões são permitidas nas expressões constantes:
- Conversões de identidade
- Conversões numéricas
- Conversões de enumeração
- Conversões de expressão constante
- Conversões de referência implícitas e explícitas, desde que a origem das conversões seja uma expressão constante avaliada como o valor
null
.
Observação: outras conversões, incluindo boxing, unboxing e conversões de referência implícita de valores que não sejam
null
, não são permitidas em expressões constantes. fim da observação
Exemplo: no código a seguir
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }
a inicialização de
i
é um erro porque uma conversão de boxing é necessária. A inicialização destr
é um erro porque uma conversão de referência implícita de um valor nãonull
é necessária.fim do exemplo
Sempre que uma expressão atender aos requisitos listados acima, a expressão será avaliada em tempo de compilação. Isso é verdadeiro mesmo se a expressão for uma subexpressão de uma expressão maior que contenha as construções não constantes.
A avaliação em tempo de compilação de expressões constantes usa as mesmas regras que a avaliação run-time de expressões não constantes, exceto quando a avaliação run-time gera uma exceção, pois nesse caso, a avaliação em tempo de compilação produzirá um erro de tempo de compilação.
A menos que uma expressão constante seja explicitamente colocada em um contexto unchecked
, estouros que ocorrem em operações e conversões aritméticas de tipo integral durante a avaliação em tempo de compilação da expressão sempre causam erros em tempo de compilação (§12.8.20).
Expressões constantes são necessárias nos contextos listados abaixo e isso é indicado na gramática usando-se constant_expression. Nesses contextos, ocorrerá um erro em tempo de compilação caso uma expressão não possa ser totalmente avaliada em tempo de compilação.
- Declarações de constante (§15.4)
- Declarações de membro de enumeração (§19.4)
- Argumentos padrão de listas de parâmetros (§15.6.2)
- Rótulos
case
de uma instruçãoswitch
(§13.8.3). - Instruções
goto case
(§13.10.4) - Tamanhos de dimensão em uma expressão de criação de matriz (§12.8.17.4) que inclui um inicializador.
- Atributos (§22)
- Em um constant_pattern (§11.2.3)
Uma conversão de expressão constante implícita (§10.2.11) permite que uma expressão constante do tipo int
seja convertida em sbyte
, byte
, short
, ushort
, uint
ou ulong
, desde que o valor da expressão constante esteja dentro do intervalo do tipo de destino.
12.24 Expressões boolianas
Um boolean_expression é uma expressão que produz um resultado do tipo bool
; diretamente ou por meio da aplicação de operator true
em alguns contextos, conforme especificado abaixo:
boolean_expression
: expression
;
A expressão condicional de controle de uma if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) ou for_statement (§13.9.4) é uma boolean_expression. A expressão condicional de controle do operador ?:
(§12.18) segue as mesmas regras de uma expressão booleana , mas, por motivos de precedência do operador, é classificada como uma expressão de coalescência nula .
Um boolean_expressionE
é necessário para se poder produzir um valor do tipo bool
da seguinte maneira:
- Se E for implicitamente conversível para
bool
, essa conversão implícita será aplicada em run-time. - Caso contrário, a resolução de sobrecarga de operador unário (§12.4.4) é utilizada para encontrar a melhor implementação única de
operator true
emE
, e essa implementação é aplicada em tempo de execução. - Se nenhum operador desse tipo for encontrado, ocorrerá um erro de tempo de associação.
ECMA C# draft specification