Partilhar via


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. Se T é um tipo anulável, T0 é o seu tipo subjacente, caso contrário, T0 é igual a T.
  • 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 de operator op em T0.
    • No contexto checked avaliação, todas as declarações de operator op verificadas e regulares em T0 exceto as declarações regulares que têm correspondência entre pares checked operator declaração.
  • Para todas as declarações operator op em U 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 argumentos A, então os operadores candidatos consistem em todos esses operadores aplicáveis em T0.
  • Caso contrário, se T0 estiver object, 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 de T0, ou a classe base efetiva de T0 se T0 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 em D que convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T. Se U 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 em D.
    • 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 em D exceto operadores de conversão explícitos regulares que tenham correspondência entre pares checked operator declaração dentro do mesmo tipo de declaração.
  • 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 em U0 que convertem de um tipo abrangente ou englobado por S para um tipo abrangente ou englobado por T. Se U 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 MethodInfocorrespondente. 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 suportar op_Addition, op_CheckedAdditione op_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:

serão substituídos pelos dois pontos seguintes:

  • O conjunto de operadores candidatos definidos pelo usuário fornecidos pelo X para a operação operator 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ção operator 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 e Y para a operação operator op(x,y) é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pela X e dos operadores candidatos fornecidos pela Y, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo, ou se X e Y 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 e Y para a operação operator op(x,y)que correspondem ao contexto atual verificado/não verificado é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pela X e dos operadores candidatos fornecidos pela Y, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo, ou se X e Y 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 e Y para a operação operator op(x,y)correspondendo ao contexto verificado/não verificado oposto é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pela X e dos operadores candidatos fornecidos pela Y, cada um determinado usando as regras do §12.4.6 - Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo, ou se X e Y 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 em D que convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T. Se U 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 em D que correspondem ao contexto atual verificado/não verificado e convertem de um tipo que engloba ou englobado por S para um tipo que engloba ou engloba por T.
  • 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. Se U0 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 em D que correspondem ao contexto oposto verificado/não verificado e convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T.
  • Encontre o conjunto de operadores de conversão definidos pelo usuário e levantados aplicáveis, U. Este conjunto consiste em operadores de U0, U1e os operadores de conversão implícitos levantados e definidos pelo usuário, declarados pelas classes ou estruturas de dados em D, que convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T. Se U 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ção operator 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 e Y para a operação operator op(x,y) é determinado. O conjunto consiste na união dos operadores candidatos fornecidos pela X e dos operadores candidatos fornecidos pela Y, cada um determinado usando as regras da seção "Operadores candidatos definidos pelo usuário" abaixo. Se X e Y forem do mesmo tipo, ou se X e Y 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. Se T é um tipo anulável, T0 é o seu tipo subjacente, caso contrário, T0 é igual a T.
  • Para todas as declarações operator opnos seus formulários verificados e regulares no contexto de avaliação checked e apenas na sua forma regular no contexto de avaliação unchecked em T0 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 argumentos A, então o conjunto de operadores candidatos é composto por todos os operadores aplicáveis em T0.
  • Caso contrário, se T0 estiver object, 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 de T0, ou a classe base efetiva de T0 se T0 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 em D que convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T. Se U 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 em Dnas suas formas verificadas e regulares no contexto de avaliação checked e apenas na sua forma regular no contexto de avaliação unchecked, 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 de U0, e nos operadores de conversão implícitos definidos pelo usuário e promovidos, declarados pelas classes ou estruturas em D, que convertem de um tipo que engloba ou é englobado por S para um tipo que engloba ou é englobado por T. Se U 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