Verificação dos operadores definidos pelo utilizador
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .
Resumo
O C# deve oferecer suporte à definição de variantes checked
dos seguintes operadores definidos pelo usuário para que os usuários possam optar por entrar ou não no comportamento de estouro, conforme apropriado:
- Os operadores unários
++
e--
§12.8.16 e §12.9.6. - O operador unário
-
§12.9.3. - Os operadores binários
+
,-
,*
e/
§12.10. - Operadores de conversão explícitos.
Motivação
Não há como um usuário declarar um tipo e oferecer suporte a versões verificadas e não verificadas de um operador. Isso tornará difícil a adaptação de vários algoritmos para usar as interfaces de generic math
propostas, expostas pela equipa de bibliotecas. Da mesma forma, isso torna impossível expor um tipo como Int128
ou UInt128
sem que a linguagem forneça simultaneamente o seu próprio suporte para evitar alterações incompatíveis.
Projeto detalhado
Sintaxe
A gramática nos operadores (§15.10) será ajustada para permitir a palavra-chave checked
após a palavra-chave operator
, imediatamente antes do token do operador:
overloadable_unary_operator
: '+' | 'checked'? '-' | '!' | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
;
overloadable_binary_operator
: 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' type identifier ')'
| 'explicit' 'operator' 'checked'? type '(' type identifier ')'
;
Por exemplo:
public static T operator checked ++(T x) {...}
public static T operator checked --(T x) {...}
public static T operator checked -(T x) {...}
public static T operator checked +(T lhs, T rhs) {...}
public static T operator checked -(T lhs, T rhs) {...}
public static T operator checked *(T lhs, T rhs) {...}
public static T operator checked /(T lhs, T rhs) {...}
public static explicit operator checked U(T x) {...}
public static T I1.operator checked ++(T x) {...}
public static T I1.operator checked --(T x) {...}
public static T I1.operator checked -(T x) {...}
public static T I1.operator checked +(T lhs, T rhs) {...}
public static T I1.operator checked -(T lhs, T rhs) {...}
public static T I1.operator checked *(T lhs, T rhs) {...}
public static T I1.operator checked /(T lhs, T rhs) {...}
public static explicit I1.operator checked U(T x) {...}
Para abreviar abaixo, um operador com a palavra-chave checked
é referido como um checked operator
e um operador sem ele é referido como um regular operator
. Estes termos não são aplicáveis a operadores que não tenham um formulário checked
.
Semântica
Espera-se que um checked operator
definido pelo usuário lance uma exceção quando o resultado de uma operação for muito grande para representar no tipo de destino. O que significa ser demasiado grande depende, na verdade, da natureza do tipo de destino e não é prescrito pela língua. Normalmente, a exceção lançada é um System.OverflowException
, mas a linguagem não tem nenhum requisito específico em relação a isso.
Espera-se que um regular operator
definido pelo usuário não lance uma exceção quando o resultado de uma operação for muito grande para representar no tipo de destino. Em vez disso, espera-se que ele retorne uma instância que represente um resultado truncado. O que significa ser demasiado grande e ser truncado, na verdade, depende da natureza do tipo de destino e não é prescrito pela língua.
Todos os operadores definidos pelo usuário existentes que terão a forma checked
suportada enquadram-se na categoria regular operators
. Entende-se que muitos deles provavelmente não seguirão a semântica especificada acima, mas para fins de análise semântica, o compilador assumirá que sim.
Contexto verificado versus contexto não verificado dentro de um checked operator
O contexto verificado/não verificado no corpo de um checked operator
não é afetado pela presença da palavra-chave checked
. Por outras palavras, o contexto é o mesmo que imediatamente no início da declaração do operador. O desenvolvedor precisaria alternar explicitamente o contexto se parte de seu algoritmo não puder confiar no contexto padrão.
Nomes em metadados
A secção "I.10.3.1 Operadores unários" da ECMA-335 será ajustada para incluir op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation como os nomes dos métodos que implementam operadores unários controlados ++
, --
e -
.
A seção "I.10.3.2 Operadores binários" do ECMA-335 será ajustada para incluir op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision como os nomes dos métodos que implementam operadores binários verificados +
, -
, *
e /
.
A secção "I.10.3.3 Operadores de conversão" do ECMA-335 será ajustada de modo a incluir op_CheckedExplicit como nome de um método de implementação de operador de conversão explícito verificado.
Operadores unários
Unary checked operators
segue as regras de §15.10.2.
Além disso, uma declaração checked operator
requer uma declaração em par de um regular operator
(o tipo de retorno também deve ser correspondente). Caso contrário, ocorre um erro em tempo de compilação.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked -(Int128 lhs);
public static Int128 operator -(Int128 lhs);
// This is fine, only a regular operator is defined
public static Int128 operator --(Int128 lhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked ++(Int128 lhs);
}
Operadores binários
Os checked operators
binários seguem as regras do §15.10.3.
Além disso, uma declaração checked operator
requer uma declaração em par de um regular operator
(o tipo de retorno também deve ser correspondente). Caso contrário, ocorre um erro em tempo de compilação.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
// This is fine, only a regular operator is defined
public static Int128 operator -(Int128 lhs, Int128 rhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Operadores candidatos definidos pelo utilizador
A seção Operadores de usuário candidatos (§12.4.6) será ajustada da seguinte forma (adições/alterações estão em negrito).
Dado um tipo T
e uma operação operator op(A)
, em que op
é um operador sobrecarregado e A
é uma lista de argumentos, o conjunto de operadores candidatos definidos pelo usuário fornecidos pela T
para operator op(A)
é determinado da seguinte forma:
- Determine o tipo
T0
. SeT
é um tipo anulável,T0
é o seu tipo subjacente, caso contrário,T0
é igual aT
. - Encontre o conjunto de operadores definidos pelo usuário,
U
. Este conjunto consiste em:-
No contexto de avaliação
unchecked
, todas as declarações regulares deoperator op
emT0
. -
No contexto
checked
avaliação, todas as declarações deoperator op
verificadas e regulares emT0
exceto as declarações regulares que têm correspondência entre pareschecked operator
declaração.
-
No contexto de avaliação
- Para todas as declarações
operator op
emU
e todas as formas elevadas desses operadores, se pelo menos um operador for aplicável (§12.4.6 - Membro da função aplicável) em relação à lista de argumentosA
, então os operadores candidatos consistem em todos esses operadores aplicáveis emT0
. - Caso contrário, se
T0
estiverobject
, o conjunto de operadores candidatos estará vazio. - Caso contrário, o conjunto de operadores candidatos fornecido pela
T0
é o conjunto de operadores candidatos fornecido pela classe base direta deT0
, ou a classe base efetiva deT0
seT0
for um parâmetro de tipo.
Regras semelhantes serão aplicadas ao determinar o conjunto de operadores candidatos em interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
A seção §12.8.20 será ajustada para refletir o efeito que o contexto verificado/não verificado tem na resolução de sobrecarga do operador unário e binário.
Exemplo #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r6 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_Division` - it is a better match than `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemplo #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemplo #3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Operadores de conversão
A conversão checked operators
segue as regras do §15.10.4.
No entanto, uma declaração checked operator
requer uma declaração em pares de um regular operator
. Caso contrário, ocorre um erro em tempo de compilação.
O parágrafo seguinte
A assinatura de um operador de conversão consiste no tipo de origem e no tipo de destino. (Esta é a única forma de membro para a qual o tipo de retorno participa da assinatura.) A classificação implícita ou explícita de um operador de conversão não faz parte da assinatura do operador. Assim, uma classe ou struct não pode declarar um operador de conversão implícito e explícito com os mesmos tipos de origem e destino.
serão ajustados para permitir que um tipo declare formas verificadas e regulares de conversões explícitas com os mesmos tipos de origem e destino. Um tipo não terá permissão para declarar um operador de conversão implícito e um operador de conversão explícito verificado com os mesmos tipos de origem e destino.
Processamento de conversões explícitas definidas pelo usuário
O terceiro ponto do §10.5.5:
- Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Esse conjunto consiste nos operadores de conversão implícitos ou explícitos definidos pelo utilizador e levantados pelas classes ou estruturas emD
que convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
serão substituídos pelos seguintes pontos:
- Encontre o conjunto de operadores de conversão,
U0
. Este conjunto consiste em:-
Em
unchecked
contexto de avaliação, os operadores de conversão explícitos ou regulares definidos pelo usuário declarados pelas classes ou structs emD
. -
Em
checked
contexto de avaliação, os operadores de conversão explícitos definidos pelo usuário implícitos ou regulares/verificados declarados pelas classes ou structs emD
exceto operadores de conversão explícitos regulares que tenham correspondência entre pareschecked operator
declaração dentro do mesmo tipo de declaração.
-
Em
- Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Esse conjunto consiste nos operadores de conversão implícitos ou explícitos definidos pelo usuário emU0
que convertem de um tipo abrangente ou englobado porS
para um tipo abrangente ou englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
A seção Operadores verificados e não verificados §11.8.20 será ajustada para refletir o efeito que o contexto verificado/não verificado tem no processamento de conversões explícitas definidas pelo usuário.
Operadores responsáveis pela execução
Um checked operator
não implementa um regular operator
e vice-versa.
Árvores de Expressão Linq
Checked operators
será suportado em Árvores de Expressão Linq. Um nó UnaryExpression
/BinaryExpression
será criado juntamente com o MethodInfo
correspondente.
Serão utilizados os seguintes métodos de fabrico:
public static UnaryExpression NegateChecked (Expression expression, MethodInfo? method);
public static BinaryExpression AddChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression SubtractChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression MultiplyChecked (Expression left, Expression right, MethodInfo? method);
public static UnaryExpression ConvertChecked (Expression expression, Type type, MethodInfo? method);
Observe que o C# não suporta atribuições em árvores de expressão, portanto, incremento/decréscimo verificado também não será suportado.
Não existe um método de fábrica para a divisão controlada. Há uma questão em aberto sobre isso - Divisão verificada em Linq Expression Trees.
Dinâmica
Investigaremos o custo de adicionar suporte para operadores verificados na invocação dinâmica no CoreCLR e buscaremos uma implementação se o custo não for muito alto. Esta é uma citação de https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.
Desvantagens
Isso adiciona complexidade adicional à linguagem e permite que os utilizadores introduzam mais tipos de alterações disruptivas nos seus tipos.
Alternativas
As interfaces matemáticas genéricas que as bibliotecas planejam expor podem expor métodos nomeados (como AddChecked
). A principal desvantagem é que isso é menos legível/sustentável e não obtém o benefício das regras de precedência de idioma em torno dos operadores.
Esta seção lista as alternativas discutidas, mas não implementadas
Posicionamento da palavra-chave checked
Em alternativa, a palavra-chave checked
pode ser movida para o local imediatamente antes da palavra-chave operator
:
public static T checked operator ++(T x) {...}
public static T checked operator --(T x) {...}
public static T checked operator -(T x) {...}
public static T checked operator +(T lhs, T rhs) {...}
public static T checked operator -(T lhs, T rhs) {...}
public static T checked operator *(T lhs, T rhs) {...}
public static T checked operator /(T lhs, T rhs) {...}
public static explicit checked operator U(T x) {...}
public static T checked I1.operator ++(T x) {...}
public static T checked I1.operator --(T x) {...}
public static T checked I1.operator -(T x) {...}
public static T checked I1.operator +(T lhs, T rhs) {...}
public static T checked I1.operator -(T lhs, T rhs) {...}
public static T checked I1.operator *(T lhs, T rhs) {...}
public static T checked I1.operator /(T lhs, T rhs) {...}
public static explicit checked I1.operator U(T x) {...}
Ou pode ser movido para o conjunto de modificadores de operador:
operator_modifier
: 'public'
| 'static'
| 'extern'
| 'checked'
| operator_modifier_unsafe
;
public static checked T operator ++(T x) {...}
public static checked T operator --(T x) {...}
public static checked T operator -(T x) {...}
public static checked T operator +(T lhs, T rhs) {...}
public static checked T operator -(T lhs, T rhs) {...}
public static checked T operator *(T lhs, T rhs) {...}
public static checked T operator /(T lhs, T rhs) {...}
public static checked explicit operator U(T x) {...}
public static checked T I1.operator ++(T x) {...}
public static checked T I1.operator --(T x) {...}
public static checked T I1.operator -(T x) {...}
public static checked T I1.operator +(T lhs, T rhs) {...}
public static checked T I1.operator -(T lhs, T rhs) {...}
public static checked T I1.operator *(T lhs, T rhs) {...}
public static checked T I1.operator /(T lhs, T rhs) {...}
public static checked explicit I1.operator U(T x) {...}
unchecked
palavra-chave
Houve sugestões para suportar a keyword unchecked
na mesma posição que a keyword checked
com os seguintes significados possíveis:
- Simplesmente para refletir explicitamente a natureza regular do operador, ou
- Talvez para designar um sabor distinto de um operador que deve ser usado num contexto
unchecked
. O idioma pode suportarop_Addition
,op_CheckedAddition
eop_UncheckedAddition
para ajudar a limitar o número de alterações de quebra. Isso adiciona outra camada de complexidade que provavelmente não é necessária na maioria dos códigos.
Nomes de operadores no ECMA-335
Alternativamente, os nomes dos operadores podem ser op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, com Checked no final. No entanto, parece que já existe um padrão estabelecido para terminar os nomes com a palavra operador. Por exemplo, há um operador op_UnsignedRightShift em vez de um operador op_RightShiftUnsigned.
Checked operators
são inaplicáveis num contexto unchecked
O compilador, ao realizar uma pesquisa de membros para encontrar operadores candidatos definidos pelo utilizador dentro de um contexto unchecked
, pode ignorar checked operators
. Se forem encontrados metadados que apenas definam um checked operator
, ocorrerá um erro de compilação.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Regras mais complicadas de pesquisa de operadores e resolução de sobrecarga num contexto checked
O compilador, ao realizar a pesquisa de membros para encontrar operadores candidatos definidos pelo usuário dentro de um contexto checked
, também considerará os operadores aplicáveis que terminam com Checked
. Ou seja, se o compilador estivesse tentando encontrar membros de função aplicáveis para o operador de adição binária, ele procuraria op_Addition
e op_AdditionChecked
. Se o único membro aplicável da função for um checked operator
, ele será usado. Se existirem regular operator
e checked operator
e forem igualmente aplicáveis, será preferível a checked operator
. Se existirem ambos regular operator
e checked operator
, com o regular operator
a ser uma correspondência exata enquanto o checked operator
não, o compilador preferirá o regular operator
.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
}
public static void Multiply(Int128 lhs, byte rhs)
{
// Resolves to `op_Multiply` even though `op_CheckedMultiply` is also applicable
Int128 r4 = checked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, int rhs);
public static Int128 operator *(Int128 lhs, byte rhs);
}
Mais uma maneira de construir o conjunto de operadores candidatos definidos pelo usuário
Resolução de sobrecarga do operador unário
Supondo que regular operator
corresponda ao contexto de avaliação de unchecked
, e checked operator
ao contexto de avaliação de checked
, e um operador que não tenha a forma checked
(por exemplo, +
) corresponda a qualquer um dos contextos, o primeiro ponto no marcador em §12.4.4 - Resolução da sobrecarga do operador unário:
- O conjunto de operadores candidatos definidos pelo usuário fornecidos pela
X
para a operaçãooperator op(x)
é determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário.
serão substituídos pelos dois pontos seguintes:
- O conjunto de operadores candidatos definidos pelo usuário fornecidos pelo
X
para a operaçãooperator op(x)
que correspondem ao contexto atual verificado/não verificado é determinado usando as regras de operadores candidatos definidos pelo usuário. - Se o conjunto de operadores candidatos definidos pelo usuário não estiver vazio, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, o conjunto de operadores candidatos definidos pelo utilizador fornecidos por
X
para a operaçãooperator op(x)
no contexto oposto de verificado/não verificado é determinado usando as regras de §12.4.6 - Operadores candidatos definidos pelo utilizador.
Resolução de sobrecarga do operador binário
Supondo que regular operator
corresponde ao contexto de avaliação unchecked
, checked operator
corresponde ao contexto de avaliação checked
e um operador que não tem uma forma checked
(por exemplo, %
) corresponde a um dos contextos, o primeiro marcador em §12.4.5 - Resolução de sobrecarga do operador binário:
- O conjunto de operadores candidatos definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pelaX
e dos operadores candidatos fornecidos pelaY
, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo, ou seX
eY
forem derivadas de um tipo de base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez.
serão substituídos pelos dois pontos seguintes:
- O conjunto de operadores candidatos definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
que correspondem ao contexto atual verificado/não verificado é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pelaX
e dos operadores candidatos fornecidos pelaY
, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo, ou seX
eY
forem derivadas de um tipo de base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez. - Se o conjunto de operadores candidatos definidos pelo usuário não estiver vazio, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, o conjunto de operadores candidatos definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
correspondendo ao contexto verificado/não verificado oposto é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pelaX
e dos operadores candidatos fornecidos pelaY
, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo, ou seX
eY
forem derivadas de um tipo de base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez.
Exemplo #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemplo #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemplo #3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemplo #4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Exemplo #5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Processamento de conversões explícitas definidas pelo usuário
Supondo que regular operator
corresponde ao unchecked
contexto de avaliação e checked operator
corresponde ao checked
contexto de avaliação, o terceiro ponto de lista em §10.5.3 Avaliação de conversões definidas pelo usuário:
- Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Esse conjunto consiste nos operadores de conversão implícitos ou explícitos definidos pelo utilizador e levantados pelas classes ou estruturas emD
que convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
serão substituídos pelos seguintes pontos:
- Encontre o conjunto de operadores de conversão explícita definidos pelo utilizador e elevados aplicáveis que correspondam ao contexto atual checado/não checado,
U0
. Esse conjunto consiste nos operadores de conversão explícitos definidos pelo usuário e levantados declarados pelas classes ou estruturas emD
que correspondem ao contexto atual verificado/não verificado e convertem de um tipo que engloba ou englobado porS
para um tipo que engloba ou engloba porT
. - Encontre o conjunto de operadores de conversão explícita definidos pelo usuário e levantados aplicáveis que correspondam ao contexto verificado/não verificado oposto,
U1
. SeU0
não estiver vazio,U1
está vazio. Caso contrário, esse conjunto consiste nos operadores de conversão explícitos definidos pelo usuário e promovidos declarados pelas classes ou estruturas emD
que correspondem ao contexto oposto verificado/não verificado e convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. - Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Este conjunto consiste em operadores deU0
,U1
e os operadores de conversão implícitos levantados e definidos pelo usuário, declarados pelas classes ou estruturas de dados emD
, que convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
Mais uma maneira de construir o conjunto de operadores candidatos definidos pelo usuário
Resolução de sobrecarga do operador unário
O primeiro ponto da secção §12.4.4 será ajustado da seguinte forma (as adições estão a negrito).
- O conjunto de operadores candidatos definidos pelo usuário fornecidos pela
X
para a operaçãooperator op(x)
é determinado usando as regras da seção "Operadores candidatos definidos pelo usuário" abaixo. Se o conjunto contiver pelo menos um operador na forma verificada, todos os operadores na forma regular são removidos do conjunto.
A seção §12.8.20 será ajustada para refletir o efeito que o contexto verificado/não verificado tem na resolução de sobrecarga do operador unário.
Resolução de sobrecarga do operador binário
O primeiro ponto da secção §12.4.5 será ajustado da seguinte forma (os aditamentos estão em negrito).
- O conjunto de operadores candidatos definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pelaX
e dos operadores candidatos fornecidos pelaY
, cada um determinado usando as regras da seção "Operadores candidatos definidos pelo usuário" abaixo. SeX
eY
forem do mesmo tipo, ou seX
eY
forem derivadas de um tipo de base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez. Se o conjunto contiver pelo menos um operador na forma verificada, todos os operadores na forma regular são removidos do conjunto.
A secção "Operadores Verificados e Não Verificados" §12.8.20 será ajustada para refletir o efeito que o contexto de verificação/não verificação tem na resolução da sobrecarga dos operadores binários.
Operadores candidatos definidos pelo utilizador
O §12.4.6 - Operadores candidatos definidos pelo usuário seção será ajustado da seguinte forma (adições estão em negrito).
Dado um tipo T
e uma operação operator op(A)
, em que op
é um operador sobrecarregado e A
é uma lista de argumentos, o conjunto de operadores candidatos definidos pelo usuário fornecidos pela T
para operator op(A)
é determinado da seguinte forma:
- Determine o tipo
T0
. SeT
é um tipo anulável,T0
é o seu tipo subjacente, caso contrário,T0
é igual aT
. - Para todas as declarações
operator op
nos seus formulários verificados e regulares no contexto de avaliaçãochecked
e apenas na sua forma regular no contexto de avaliaçãounchecked
emT0
e todas as formas promovidas desses operadores, se pelo menos um operador for aplicável (§12.6.4.2) no que diz respeito à lista de argumentosA
, então o conjunto de operadores candidatos é composto por todos os operadores aplicáveis emT0
. - Caso contrário, se
T0
estiverobject
, o conjunto de operadores candidatos estará vazio. - Caso contrário, o conjunto de operadores candidatos fornecido pela
T0
é o conjunto de operadores candidatos fornecido pela classe base direta deT0
, ou a classe base efetiva deT0
seT0
for um parâmetro de tipo.
Filtragem semelhante será aplicada ao determinar o conjunto de operadores candidatos em interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
A seção §12.8.20 será ajustada para refletir o efeito que o contexto verificado/não verificado tem na resolução de sobrecarga dos operadores unários e binários.
Exemplo #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Exemplo #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemplo #3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Exemplo #4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Exemplo #5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Processamento de conversões explícitas definidas pelo usuário
O terceiro ponto do §10.5.5:
- Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Esse conjunto consiste nos operadores de conversão implícitos ou explícitos definidos pelo utilizador e levantados pelas classes ou estruturas emD
que convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
serão substituídos pelos seguintes pontos:
- Encontre o conjunto de operadores de conversão explícitos definidos pelo usuário e levantados aplicáveis,
U0
. Este conjunto consiste nos operadores de conversão explícitos, definidos pelo utilizador e levantados, declarados pelas classes ou estruturas emD
nas suas formas verificadas e regulares no contexto de avaliaçãochecked
e apenas na sua forma regular no contexto de avaliaçãounchecked
, e convertem de um tipo que engloba, ou é englobado por,S
para um tipo que engloba, ou é englobado por,T
. - Se
U0
contiver pelo menos um operador na forma verificada, todos os operadores na forma regular são removidos do conjunto. - Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis,
U
. Este conjunto consiste em operadores deU0
, e nos operadores de conversão implícitos definidos pelo usuário e promovidos, declarados pelas classes ou estruturas emD
, que convertem de um tipo que engloba ou é englobado porS
para um tipo que engloba ou é englobado porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro em tempo de compilação.
A seção Operadores verificados e não verificados §12.8.20 será ajustada para refletir o efeito que o contexto verificado/não verificado tem no processamento de conversões explícitas definidas pelo usuário.
Contexto verificado versus contexto não verificado dentro de um checked operator
O compilador pode tratar o contexto padrão de um checked operator
como estando verificado. O desenvolvedor precisaria usar explicitamente unchecked
se parte de seu algoritmo não deveria participar do checked context
. No entanto, isso pode não funcionar bem no futuro se começarmos a permitir que tokens checked
/unchecked
como modificadores de operadores definam o contexto dentro do corpo. O modificador e a palavra-chave podem contradizer-se. Além disso, não poderíamos fazer o mesmo (tratar o contexto padrão como desmarcado) para um regular operator
porque isso seria uma mudança que quebraria.
Questões por resolver
A linguagem deve permitir modificadores checked
e unchecked
em métodos (por exemplo, static checked void M()
)?
Isso permitiria remover os níveis de aninhamento para métodos que o exigem.
Divisão verificada em Linq Expression Trees
Não há nenhum método de fábrica para criar um nó de divisão verificado e não há nenhum membro ExpressionType.DivideChecked
.
Ainda podemos utilizar o seguinte método de fábrica para criar um nó de divisão regular, com MethodInfo
apontando para o método op_CheckedDivision
.
Os consumidores terão de verificar o nome para inferir o contexto.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Observe que, embora seção §12.8.20 liste /
operador como um dos operadores afetados pelo contexto de avaliação verificado/não verificado, o IL não tem um código operacional especial para executar a divisão verificada.
O compilador está a usar sempre o método de fábrica independentemente do contexto hoje.
Proposta: A divisão definida pelo utilizador, quando verificada, não será suportada nas Árvores de Expressão Linq.
(Resolvido) Devemos apoiar operadores de conversão verificados implícitos?
Em geral, os operadores de conversão implícitos não devem lançar.
Proposta: nº.
Resolução: Aprovado - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions
Reuniões de design
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md
C# feature specifications