Compartilhar via


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. Se T for um tipo anulável, T0 será seu tipo subjacente; caso contrário, T0 será igual a T.
  • Localize o conjunto de operadores definidos pelo usuário, U. Esse conjunto consiste em:
    • No contexto de avaliação unchecked, todas as declarações regulares operator op em T0.
    • No contexto de avaliação checked, todas as declarações verificadas e regularesoperator op em T0, exceto as declarações regulares que têm uma declaração pareada checked operator correspondente.
  • Para todas as declarações operator op em U 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 argumentos A, o conjunto de operadores candidatos consiste em todos esses operadores aplicáveis em T0.
  • Caso contrário, se T0 for igual a object, 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 de T0ou 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 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 em D, que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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 em D.
    • 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 em D exceto operadores de conversão explícita regular que têm uma declaração checked operator de correspondência pareada dentro do mesmo tipo de declaraçã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 em U0 que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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 a op_Addition, op_CheckedAdditione op_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:

será substituído pelos dois marcadores seguintes:

  • O conjunto de operadores de candidato definidos pelo usuário fornecidos por X para a operação operator 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ção operator 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 e Y para a operação operator op(x,y) é determinado. O conjunto consiste na união dos operadores candidatos fornecidos por X e os operadores candidatos fornecidos por Y, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo ou se X e Y 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 e Y para a operação operator op(x,y)que corresponde ao contexto verificado/não verificado atual é determinado. O conjunto consiste na união dos operadores candidatos fornecidos por X e os operadores candidatos fornecidos por Y, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo ou se X e Y 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 e Y para a operação operator op(x,y)que corresponde ao contexto verificado/não verificado oposto é determinado. O conjunto consiste na união dos operadores candidatos fornecidos por X e os operadores candidatos fornecidos por Y, cada um determinado usando as regras de §12.4.6 – Operadores candidatos definidos pelo usuário. Se X e Y forem do mesmo tipo ou se X e Y 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 em D, que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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 em D que correspondem ao contexto verificado/não verificado atual e convertem de um tipo que abrange ou ou é abrangido por S em um tipo que abrange ou é abrangido por T.
  • 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. Se U0 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 em D que correspondem ao contexto verificado/não verificado oposto e convertem de um tipo que abrange ou é abrangido por S em um tipo que abrange ou é abrangido por T.
  • Encontre o conjunto de operadores de conversão aplicáveis, definidos pelo usuário e elevados, U. Esse conjunto consiste nos operadores de U0, U1 e operadores de conversão implícita definidos pelo usuário, declarados por classes ou structs em D, que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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ção operator 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 e Y para a operação operator op(x,y) é determinado. O conjunto consiste na união dos operadores candidatos fornecidos por X e os operadores candidatos fornecidos por Y, cada um determinado usando as regras da seção "Operadores definidos pelo usuário candidatos" abaixo. Se X e Y forem do mesmo tipo ou se X e Y 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. Se T for um tipo anulável, T0 será seu tipo subjacente; caso contrário, T0 será igual a T.
  • Para todas as declarações operator op em suas formas verificadas 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 elevadas desses operadores, se pelo menos um operador for aplicável (§12.6.4.2) em relação à lista de argumentos A, o conjunto de operadores de candidato inclui todos esses operadores aplicáveis em T0.
  • Caso contrário, se T0 for object, 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 de T0ou 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 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 em D, que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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 em Dem suas formas verificadas e regulares no contexto de avaliação checked e apenas em sua forma regular no contexto de avaliação unchecked, e convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T.
  • 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 de U0 e operadores de conversão implícita definidos pelo usuário, declarados por classes ou structs em D, que convertem de um tipo que abrange ou é abrangido por S para um tipo que abrange ou é abrangido por T. Se U 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