Operadores verificados definidos pelo usuário
Nota
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação 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 reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de especificações de funcionalidades no padrão de linguagem C# no artigo sobre as especificações de .
Problema do especialista: https://github.com/dotnet/csharplang/issues/4665
Resumo
C# deve dar suporte à definição de variantes checked
dos seguintes operadores definidos pelo usuário, para que os usuários possam escolher entre adotar ou não adotar o comportamento de estouro conforme apropriado:
- Os operados 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ícita.
Motivação
Não é possível que um usuário declare um tipo e dê suporte a versões checadas e não checadas de um operador. Isso tornará difícil portar vários algoritmos para usar as interfaces generic math
propostas, expostas pela equipe responsável pelas bibliotecas. Da mesma forma, isso torna impossível expor um tipo como Int128
ou UInt128
sem que a linguagem ofereça simultaneamente seu próprio suporte para evitar alterações disruptivas.
Design detalhado
Sintaxe
A gramática nos operadores (§15.10) será ajustada para permitir a palavra-chave checked
após a palavra-chave operator
, logo antes do símbolo de 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 obter a brevidade abaixo, um operador com a palavra-chave checked
é conhecido como um checked operator
e um operador sem ele é chamado de regular operator
. Esses termos não são aplicáveis a operadores que não têm um formulário checked
.
Semântica
Espera-se que um checked operator
definido pelo usuário gere uma exceção quando o resultado de uma operação for muito grande para representar no tipo de destino. O que significa ser muito grande realmente depende da natureza do tipo de destino e não é prescrito pela linguagem. Normalmente, a exceção gerada é um System.OverflowException
, mas o idioma não tem requisitos específicos 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 ser representado no tipo de destino. Em vez disso, espera-se que ele retorne uma instância que representa um resultado truncado. O que significa ser muito grande e truncado depende, na verdade, da natureza do tipo de destino e não é prescrito pela linguagem.
Todos os operadores definidos pelo usuário existentes que terão suporte com o formato checked
se enquadram na categoria de 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 eles são.
Contexto verificado versus não verificado em checked operator
O contexto verificado/não verificado no corpo de checked operator
não é afetado pela presença da palavra-chave checked
. Em 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 do algoritmo não puder depender do contexto padrão.
Nomes em metadados
A seção "I.10.3.1 Operadores unários" do ECMA-335 será ajustada para incluir op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation como os nomes dos métodos que implementam os operadores unários verificados ++
, --
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 seção "Operadores de conversão I.10.3.3" do ECMA-335 será ajustada para incluir op_CheckedExplicit como o nome de um método que implementa o operador de conversão explícita verificado.
Operadores unários
Os unários checked operators
seguem as regras de §15.10.2.
Além disso, uma declaração checked operator
requer uma declaração pareada de regular operator
(o tipo de retorno também deve corresponder). Caso contrário, ocorre um erro de 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 binários checked operators
seguem as regras de §15.10.3.
Além disso, uma declaração checked operator
requer uma declaração pareada de regular operator
(o tipo de retorno também deve corresponder). Caso contrário, ocorre um erro de 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 de candidato definidos pelo usuário
A seção Operadores de candidato definidos pelo usuário (§12.4.6) será ajustada da seguinte maneira (adições/alterações estão em negrito).
Considerando 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 definidos pelo usuário candidato fornecido por T
para operator op(A)
é determinado da seguinte maneira:
- Determine o tipo
T0
. SeT
for um tipo anulável,T0
será seu tipo subjacente; caso contrário,T0
será igual aT
. - Localize o conjunto de operadores definidos pelo usuário,
U
. Esse conjunto consiste em:- No contexto de avaliação
unchecked
, todas as declarações regularesoperator op
emT0
. - No contexto de avaliação
checked
, todas as declarações verificadas e regularesoperator op
emT0
, exceto as declarações regulares que têm uma declaração pareadachecked operator
correspondente.
- No contexto de avaliação
- Para todas as declarações
operator op
emU
e todas as formas levantadas 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
, o conjunto de operadores candidatos consiste em todos esses operadores aplicáveis emT0
. - Caso contrário, se
T0
for igual aobject
, o conjunto de operadores candidatos estará vazio. - Caso contrário, o conjunto de operadores candidatos fornecido pelo
T0
é o conjunto de operadores candidatos fornecidos 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 de operadores unários e binários.
Exemplo nº 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 nº 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 nº 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 de §15.10.4.
No entanto, uma declaração checked operator
requer uma declaração pareada de regular operator
. Caso contrário, ocorre um erro de tempo de compilação.
O parágrafo a seguir
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. Portanto, 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 de destino.
será ajustado 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 explícita implícito e verificado com os mesmos tipos de origem e de destino.
Processamento de conversões explícitas definidas pelo usuário
O terceiro marcador em §10.5.5:
- Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste de operadores de conversão implícita ou explícita definidos pelo usuário, declarados por classes ou structs emD
, que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de tempo de compilação.
será substituído pelos seguintes marcadores:
- Localize o conjunto de operadores de conversão,
U0
. Esse conjunto consiste em:- No contexto de avaliação
unchecked
, os operadores de conversão implícita ou regular explícita definidos pelo usuário declarados pelas classes ou structs emD
. - No contexto de avaliação
checked
, os operadores de conversão implícita ou explícita regular/verificados definidos pelo usuário declarados pelas classes ou structs emD
exceto operadores de conversão explícita regular que têm uma declaraçãochecked operator
de correspondência pareada dentro do mesmo tipo de declaração.
- No contexto de avaliação
- Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste nos operadores de conversão implícita ou explícita elevados definidos pelo usuário emU0
que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de 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 sobre o processamento de conversões explícitas definidas pelo usuário.
Implementando operadores
checked operator
não implementa regular operator
e vice-versa.
Árvores de expressão Linq
Checked operators
terá suporte em Árvores de Expressão Linq. Um nó UnaryExpression
/BinaryExpression
será criado com o MethodInfo
correspondente.
Os seguintes métodos de fábrica serão usados:
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 dá suporte a atribuições em árvores de expressão, portanto, o incremento/decremento verificado também não terá suporte.
Não há nenhum método de fábrica para divisão verificada. Há uma pergunta aberta sobre isso – Divisão verificada em árvores de expressão Linq.
Dinâmico
Investigaremos o custo de adição de suporte para operadores verificados na invocação dinâmica no CoreCLR e prosseguiremos com 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.
Inconvenientes
Isso adiciona mais complexidade à linguagem e permite que os usuários introduzam mais tipos de alterações interruptivas em 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/mantenível e não obtém o benefício das regras de precedência da linguagem em torno dos operadores.
Esta seção lista alternativas discutidas, mas não implementadas
Posicionamento da palavra-chave checked
Como alternativa, a palavra-chave checked
pode ser movida para o lugar 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 dar suporte à palavra-chave unchecked
na mesma posição que a palavra-chave checked
, com os seguintes significados possíveis:
- Simplesmente para refletir explicitamente a natureza regular do operador ou
- Talvez para designar uma variedade distinta de um operador que deveria ser usado em um contexto
unchecked
. O idioma pode dar suporte aop_Addition
,op_CheckedAddition
eop_UncheckedAddition
para ajudar a limitar o número de alterações interruptivas. Isso adiciona outra camada de complexidade que provavelmente não é necessária na maioria dos códigos.
Nomes de operador no padrão ECMA-335
Como alternativa, 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 encerrar os nomes com a palavra do operador. Por exemplo, existe um operador op_UnsignedRightShift em vez de um operador op_RightShiftUnsigned.
Checked operators
não são aplicáveis em um contexto unchecked
O compilador, ao executar a pesquisa de membro para localizar operadores candidatos definidos pelo usuário em um contexto de unchecked
, pode ignorar checked operators
. Se forem encontrados metadados que definem apenas 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 de resolução de sobrecarga em um contexto checked
O compilador, ao executar a pesquisa de membro para localizar operadores candidatos definidos pelo usuário em um contexto checked
também considerará operadores aplicáveis que terminam com Checked
. Ou seja, se o compilador estava 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 da função aplicável for checked operator
, ele será usado. Se um regular operator
e checked operator
existirem e forem igualmente aplicáveis, o checked operator
será preferencial. Se regular operator
e checked operator
existirem, mas regular operator
for uma correspondência exata, enquanto checked operator
não é, o compilador vai preferir 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 forma de criar o conjunto de candidatos a operadores definidos pelo usuário
Resolução de sobrecarga do operador unário
Supondo que regular operator
corresponda ao contexto de avaliação unchecked
, checked operator
corresponda ao contexto de avaliação checked
e um operador que não tenha a forma checked
(por exemplo, +
) corresponda a qualquer um dos contextos, o primeiro marcador em §12.4.4 – Resolução de sobrecarga do operador unário:
- O conjunto de operadores definidos pelo usuário candidatos fornecidos por
X
para a operaçãooperator op(x)
é determinado usando as regras de §12.4.6 – Candidatos a operadores definidos pelo usuário.
será substituído pelos dois marcadores seguintes:
- O conjunto de operadores de candidato definidos pelo usuário fornecidos por
X
para a operaçãooperator op(x)
que corresponde ao contexto verificado/não verificado atual é determinado usando as regras dos operadores de candidato definidos pelo usuário . - Se o conjunto de operadores definidos pelo usuário candidato não estiver vazio, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, o conjunto de candidatos a operadores definidos pelo usuário fornecidos por
X
para a operaçãooperator op(x)
que corresponde ao contexto oposto checado/desmarcado é determinado usando as regras de §12.4.6 – Candidatos a operadores definidos pelo usuário.
Resolução de sobrecarga do operador binário
Supondo que regular operator
corresponda ao contexto de avaliação unchecked
, checked operator
corresponda ao contexto de avaliação checked
e um operador que não tenha a forma checked
(por exemplo, %
) corresponda a qualquer um dos contextos, o primeiro marcador em §12.4.5 – Resolução de sobrecarga do operador binário:
- O conjunto de operadores definidos pelo usuário candidatos fornecidos por
X
eY
para a operaçãooperator op(x,y)
é determinado. O conjunto consiste na união dos operadores candidatos fornecidos porX
e os operadores candidatos fornecidos porY
, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo ou seX
eY
forem derivados de um tipo base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez.
será substituído pelos dois marcadores seguintes:
- O conjunto de operadores de candidato definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
que corresponde ao contexto verificado/não verificado atual é determinado. O conjunto consiste na união dos operadores candidatos fornecidos porX
e os operadores candidatos fornecidos porY
, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo ou seX
eY
forem derivados de um tipo base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez. - Se o conjunto de operadores definidos pelo usuário candidato não estiver vazio, isso se tornará o conjunto de operadores candidatos para a operação. Caso contrário, o conjunto de operadores de candidato definidos pelo usuário fornecidos por
X
eY
para a operaçãooperator op(x,y)
que corresponde ao contexto verificado/não verificado oposto é determinado. O conjunto consiste na união dos operadores candidatos fornecidos porX
e os operadores candidatos fornecidos porY
, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. SeX
eY
forem do mesmo tipo ou seX
eY
forem derivados de um tipo base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez.
Exemplo nº 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 nº 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 nº 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 nº 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 nº 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
corresponda ao contexto de avaliação unchecked
e checked operator
corresponda ao contexto de avaliação checked
, o terceiro marcador na seção §10.5.3 Avaliação de conversões definidas pelo usuário:
- Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste de operadores de conversão implícita ou explícita definidos pelo usuário, declarados por classes ou structs emD
, que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de tempo de compilação.
será substituído pelos seguintes marcadores:
- Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados correspondentes ao contexto verificado/não verificado atual,
U0
. Esse conjunto consiste nos operadores de conversão explícita definidos pelo usuário e elevados declarados pelas classes ou structs emD
que correspondem ao contexto verificado/não verificado atual e convertem de um tipo que abrange ou ou é abrangido porS
em um tipo que abrange ou é abrangido porT
. - Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados correspondentes ao contexto verificado/não verificado oposto,
U1
. SeU0
não estiver vazio,U1
estará vazio. Caso contrário, esse conjunto consiste nos operadores de conversão explícita definidos pelo usuário e elevados declarados pelas classes ou structs emD
que correspondem ao contexto verificado/não verificado oposto e convertem de um tipo que abrange ou é abrangido porS
em um tipo que abrange ou é abrangido porT
. - Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste nos operadores deU0
,U1
e operadores de conversão implícita definidos pelo usuário, declarados por classes ou structs emD
, que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de tempo de compilação.
Mais uma nova maneira de criar o conjunto de candidatos a operadores definidos pelo usuário
Resolução de sobrecarga do operador unário
O primeiro marcador na seção §12.4.4 será ajustado da seguinte maneira (as adições estão em negrito).
- O conjunto de operadores candidatos definidos pelo usuário fornecidos por
X
para a operaçãooperator op(x)
é determinado usando as regras da seção "Operadores Definidos Pelo Usuário Candidatos" abaixo. Se o conjunto contiver pelo menos um operador no formulário verificado, todos os operadores em forma regular serã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 de operadores unários.
Resolução de sobrecarga do operador binário
O primeiro marcador na seção §12.4.5 será ajustado da seguinte maneira (as adições estão em negrito).
- O conjunto de operadores definidos pelo usuário candidatos fornecidos por
X
eY
para a operaçãooperator op(x,y)
é determinado. O conjunto consiste na união dos operadores candidatos fornecidos porX
e os operadores candidatos fornecidos porY
, cada um determinado usando as regras da seção "Operadores definidos pelo usuário candidatos" abaixo. SeX
eY
forem do mesmo tipo ou seX
eY
forem derivados de um tipo base comum, os operadores candidatos compartilhados só ocorrerão no conjunto combinado uma vez. Se o conjunto contiver pelo menos um operador no formulário verificado, todos os operadores em forma regular serão removidos do conjunto.
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 na resolução de sobrecarga de operadores unários e binários.
Operadores de candidato definidos pelo usuário
A seção §12.4.6 – Operadores de candidato definidos pelo usuário será ajustada da seguinte maneira (adições/alterações estão em negrito).
Considerando 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 definidos pelo usuário candidato fornecido por T
para operator op(A)
é determinado da seguinte maneira:
- Determine o tipo
T0
. SeT
for um tipo anulável,T0
será seu tipo subjacente; caso contrário,T0
será igual aT
. - Para todas as declarações
operator op
em suas formas verificadas e regulares no contexto de avaliaçãochecked
e apenas na sua forma regular no contexto de avaliaçãounchecked
emT0
e todas as formas elevadas desses operadores, se pelo menos um operador for aplicável (§12.6.4.2) em relação à lista de argumentosA
, o conjunto de operadores de candidato inclui todos esses operadores aplicáveis emT0
. - Caso contrário, se
T0
forobject
, o conjunto de operadores candidatos estará vazio. - Caso contrário, o conjunto de operadores candidatos fornecido pelo
T0
é o conjunto de operadores candidatos fornecidos 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 de operadores unários e binários.
Exemplo nº 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 nº 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 nº 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 nº 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 nº 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 marcador em §10.5.5:
- Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste de operadores de conversão implícita ou explícita definidos pelo usuário, declarados por classes ou structs emD
, que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de tempo de compilação.
será substituído pelos seguintes marcadores:
- Encontre o conjunto de operadores de conversão explícita aplicáveis, definidos pelo usuário e elevados,
U0
. Esse conjunto consiste nos operadores de conversão explícita definidos pelo usuário e elevados declarados pelas classes ou structs emD
em suas formas verificadas e regulares no contexto de avaliaçãochecked
e apenas em sua forma regular no contexto de avaliaçãounchecked
, e convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. - Se
U0
contiver pelo menos um operador em formato verificado, todos os operadores em formato regular serão removidos do conjunto. - Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados,
U
. Esse conjunto consiste nos operadores deU0
e operadores de conversão implícita definidos pelo usuário, declarados por classes ou structs emD
, que convertem de um tipo que abrange ou é abrangido porS
para um tipo que abrange ou é abrangido porT
. SeU
estiver vazio, a conversão será indefinida e ocorrerá um erro de 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 sobre o processamento de conversões explícitas definidas pelo usuário.
Contexto verificado versus não verificado em checked operator
O compilador pode tratar o contexto padrão de checked operator
como verificado. O desenvolvedor precisaria usar explicitamente unchecked
se parte de seu algoritmo não participasse do checked context
. No entanto, isso poderá não funcionar bem no futuro se começarmos a permitir os tokens checked
/unchecked
como modificadores em operadores para definir o contexto no corpo. O modificador e a palavra-chave podem se contradizer. Além disso, não seria possível fazer o mesmo (tratar o contexto padrão como não verificado) para regular operator
porque isso seria uma alteração significativa.
Perguntas não resolvidas
O idioma deve permitir modificadores checked
e unchecked
em métodos (por exemplo, static checked void M()
)?
Isso permitiria a remoção de níveis de aninhamento para métodos que exigem isso.
Divisão verificada em árvores de expressão Linq
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 é possível usar o método de fábrica a seguir para criar um nó de divisão regular com MethodInfo
apontando para o método op_CheckedDivision
.
Os consumidores terão que verificar o nome para inferir o contexto.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Observe que, embora a seção 12.8.20 liste o operador /
como um dos operadores afetados pelo contexto de avaliação verificado/não verificado, IL não tem um código de operação especial para executar a divisão verificada.
O compilador sempre usa o método de fábrica independentemente do contexto.
Proposta: não haverá suporte para divisão verificada definida pelo usuário em Árvores de Expressão Linq.
(Resolvido) Devemos dar suporte a operadores de conversão verificados implícitos?
Em geral, os operadores de conversão implícita não devem ser lançados.
Proposta: Não.
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