Condividi tramite


Operatori definiti dall'utente verificati.

Nota

Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze sono annotate nelle note pertinenti del language design meeting (LDM) .

Puoi trovare maggiori informazioni sul processo di adozione degli speclet delle funzionalità nello standard del linguaggio C# nell'articolo riguardante le specifiche di .

Sommario

C# dovrebbe supportare la definizione di varianti checked dei seguenti operatori definiti dall'utente, così che gli utenti possano scegliere di abilitare o disabilitare il comportamento di overflow in base alle loro esigenze.

Motivazione

Non è possibile per un utente dichiarare un tipo e supportare sia le versioni controllate che non controllate di un operatore. In questo modo sarà difficile adattare vari algoritmi per usare le interfacce proposte di generic math rese disponibili dal team delle librerie. Allo stesso modo, ciò rende impossibile esporre un tipo, ad esempio Int128 o UInt128 senza che il linguaggio fornisca contemporaneamente il proprio supporto per evitare modifiche radicali.

Progettazione dettagliata

Sintassi

La grammatica negli operatori (§15.10), verrà modificata in modo da consentire la parola chiave checked dopo la parola chiave operator immediatamente prima del token dell'operatore.

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 ')'
    ;    

Per esempio:

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) {...}

Per brevità, di seguito, un operatore con la parola chiave checked è definito come checked operator e un operatore senza questa è definito come regular operator. Questi termini non sono applicabili agli operatori che non hanno un modulo checked.

Semantica

È previsto che un checked operator definito dall'utente generi un'eccezione quando il risultato di un'operazione è troppo grande per essere rappresentato nel tipo di destinazione. Cosa significhi essere troppo grandi dipende effettivamente dalla natura del tipo di destinazione e non è prescritto dal linguaggio. In genere l'eccezione generata è un System.OverflowException, ma il linguaggio non ha requisiti specifici relativi a questo.

È previsto che un regular operator personalizzato dall'utente non generi un'eccezione quando il risultato di un'operazione è troppo grande per essere rappresentato nel tipo di destinazione. Si prevede invece che restituisca un'istanza che rappresenta un risultato troncato. Ciò che significa essere troppo grande e essere troncato dipende dalla natura del tipo destinatario e non è prescritto dalla lingua.

Tutti gli operatori definiti dall'utente esistenti che avranno il modulo checked supportato rientrano nella categoria regular operators. Si è capito che molti di essi probabilmente non seguono la semantica specificata in precedenza, ma ai fini dell'analisi semantica, il compilatore presupporrà che siano.

Contesto controllato e non controllato all'interno di un checked operator

Il contesto selezionato/deselezionato all'interno del corpo di un checked operator non è influenzato dalla presenza della parola chiave checked. In altre parole, il contesto corrisponde immediatamente all'inizio della dichiarazione dell'operatore. Lo sviluppatore dovrà cambiare in modo esplicito il contesto se parte dell'algoritmo non può basarsi sul contesto predefinito.

Nomi nei metadati

La sezione "I.10.3.1 Operatori unari" di ECMA-335 verrà modificata per includere op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation come nomi per i metodi che implementano i ++, -- e - operatori unari controllati.

La sezione "I.10.3.2 Operatori binari" di ECMA-335 verrà modificata per includere op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision come nomi per i metodi che implementano gli operatori binari controllati +, -, *e /.

La sezione "Operatori di conversione I.10.3.3" di ECMA-335 verrà modificata in modo da includere op_CheckedExplicit come nome per un metodo che implementa l'operatore di conversione esplicito controllato.

Operatori unari

Gli unari checked operators seguono le regole di §15.10.2.

Inoltre, una dichiarazione di checked operator richiede una dichiarazione pair-wise di un regular operator (anche il tipo restituito deve corrispondere). In caso contrario, si verifica un errore in fase di compilazione.

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);
}

Operatori binari

I binari checked operators seguono le regole da §15.10.3.

Inoltre, una dichiarazione di checked operator richiede una dichiarazione pair-wise di un regular operator (anche il tipo restituito deve corrispondere). In caso contrario, si verifica un errore in fase di compilazione.

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);
}

Operatori candidati definiti dall'utente

Gli operatori utente candidati (sezione §12.4.6) verranno modificati come indicato di seguito (aggiunte/modifiche in grassetto).

Dato un tipo T e un'operazione operator op(A), dove op è un operatore sovraccaricabile e A è una lista di argomenti, il set di operatori candidati definiti dall'utente forniti da T per operator op(A) viene determinato come segue:

  • Determinare il tipo T0. Se T è un tipo nullable, T0 è il tipo sottostante; in caso contrario, T0 è uguale a T.
  • Trova il set di operatori definiti dall'utente U. Questo set è costituito da:
    • Nel contesto di valutazione unchecked tutte le normali dichiarazioni di operator op in T0.
    • Nel contesto di valutazione checked, tutte le dichiarazioni controllate e regolari di operator op in T0, ad eccezione delle dichiarazioni regolari con dichiarazioni checked operator corrispondenti a coppie.
  • Per tutte le dichiarazioni di operator op in U e per tutte le forme sollevate di tali operatori, se almeno un operatore è applicabile (§12.4.6 - Membro di funzione applicabile) relativamente alla lista di argomenti A, allora il set di operatori candidati comprende tutti tali operatori applicabili in T0.
  • In caso contrario, se T0 è object, l'insieme di operatori candidati è vuoto.
  • In caso contrario, il set di operatori candidati forniti da T0 è il set di operatori candidati forniti dalla classe base diretta di T0o dalla classe base effettiva di T0 se T0 è un parametro di tipo.

Verranno applicate regole simili durante la determinazione del set di operatori candidati nelle interfacce https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

La sezione §12.8.20 verrà modificata per riflettere l'effetto del contesto controllato/non controllato sulla risoluzione del sovraccarico dell'operatore unario e binario.

Esempio 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);
}

Esempio 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
{
}

Esempio 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
{
}

Operatori di conversione

La conversione checked operators segue le regole di §15.10.4.

Tuttavia, una dichiarazione di checked operator richiede una dichiarazione a livello di coppia di un regular operator. In caso contrario, si verifica un errore in fase di compilazione.

Paragrafo seguente

La firma di un operatore di conversione è costituita dal tipo di origine e dal tipo di destinazione. Si tratta dell'unico tipo di membro per il quale il tipo restituito partecipa alla firma. La classificazione implicita o esplicita di un operatore di conversione non fa parte della firma dell'operatore. Pertanto, una classe o uno struct non può dichiarare un operatore di conversione implicito ed esplicito con gli stessi tipi di origine e di destinazione.

verrà modificato in modo da consentire a un tipo di dichiarare forme regolari e controllate di conversioni esplicite con gli stessi tipi di origine e di destinazione. Un tipo non potrà dichiarare sia un operatore di conversione implicito che un operatore di conversione esplicito controllato con gli stessi tipi di origine e di destinazione.

Elaborazione di conversioni esplicite definite dall'utente

Terzo elemento dell'elenco in §10.5.5:

  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct in D che convertono da un tipo che include o è incluso in S a un tipo che include o è incluso in T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

verranno sostituiti con i punti elenco seguenti:

  • Trova il set di Operatori di conversione, U0. Questo set è costituito da:
    • In unchecked contesto di valutazione, gli operatori di conversione impliciti o regolari definiti dall'utente dichiarati dalle classi o dagli struct in D.
    • In checked contesto di valutazione, gli operatori di conversione impliciti o espliciti, normali o controllati, definiti dall'utente, dichiarati dalle classi o dagli struct in D, tranne gli operatori di conversione espliciti che hanno una dichiarazione di corrispondenza di coppia checked operator all'interno dello stesso tipo dichiarante.
  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente o sollevati in U0 che effettuano la conversione da un tipo che include o è incluso in S a un tipo che include o è incluso in T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

Gli operatori checked e unchecked nella sezione §11.8.20 verranno modificati per riflettere l'effetto che il contesto checked/unchecked ha sull'elaborazione delle conversioni esplicite definite dall'utente.

Implementazione degli operatori

Un checked operator non implementa un regular operator e viceversa.

Alberi delle espressioni Linq

Checked operators sarà supportato negli alberi delle espressioni LINQ. Verrà creato un nodo UnaryExpression/BinaryExpression con il corrispondente MethodInfo. Verranno usati i metodi factory seguenti:

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);

Si noti che C# non supporta le assegnazioni negli alberi delle espressioni, pertanto anche l'incremento/decremento controllato non sarà supportato.

Non esiste alcun metodo di fabbrica per la divisione controllata. C'è una domanda aperta riguardo a questo: divisione verificata negli alberi delle espressioni LINQ.

Dinamico

Esamineremo il costo dell'aggiunta del supporto per gli operatori controllati nell'invocazione dinamica in CoreCLR e procederemo con un'implementazione se il costo non è troppo elevato. Si tratta di una citazione da https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.

Svantaggi

In questo modo si aggiunge una maggiore complessità al linguaggio e consente agli utenti di introdurre più tipi di modifiche di rilievo ai loro tipi.

Alternative

Le interfacce matematiche generiche che le librerie prevedono di presentare potrebbero includere metodi denominati, ad esempio AddChecked. Lo svantaggio principale è che questo è meno leggibile/gestibile e non ottiene il vantaggio delle regole di precedenza del linguaggio relative agli operatori.

Questa sezione elenca le alternative descritte, ma non implementate

Posizionamento della parola chiave checked

In alternativa, la parola chiave checked può essere spostata nella posizione immediatamente precedente alla parola chiave 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) {...}

In alternativa, potrebbe essere spostato nel set di modificatori di operatore:

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) {...}

Parola chiave unchecked

Sono state proposte per supportare la parola chiave unchecked nella stessa posizione della parola chiave checked con i seguenti possibili significati:

  • Semplicemente per riflettere in modo più esplicito il carattere regolare dell'operatore o
  • Forse per designare una variante distinta di un operatore che dovrebbe essere usato in un contesto unchecked. Il linguaggio può supportare op_Addition, op_CheckedAdditione op_UncheckedAddition per limitare il numero di modifiche di rilievo. In questo modo viene aggiunto un altro livello di complessità che probabilmente non è necessario nella maggior parte del codice.

Nomi degli operatori in ECMA-335

In alternativa, i nomi degli operatori possono essere op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, ovvero con Checked alla fine. Tuttavia, sembra che esista già un modello stabilito per terminare i nomi con la parola dell'operatore. Ad esempio, esiste un operatore op_UnsignedRightShift anziché un operatore op_RightShiftUnsigned.

Checked operators sono inapplicabili in un contesto di unchecked

Il compilatore, durante l'esecuzione della ricerca dei membri per trovare gli operatori candidati definiti dall'utente all'interno di un contesto di unchecked, potrebbe ignorare checked operators. Se vengono rilevati metadati che definiscono solo un checked operator, si verificherà un errore di compilazione.

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);
}

Regole più complesse per la ricerca degli operatori e la risoluzione dell'overload in un contesto checked

Il compilatore, quando si esegue la ricerca dei membri per trovare gli operatori candidati definiti dall'utente all'interno di un contesto checked considererà anche gli operatori applicabili che terminano con Checked. In altre parole, se il compilatore tentava di trovare i membri di funzione applicabili per l'operatore di addizione binaria, cercherebbe sia op_Addition che op_AdditionChecked. Se l'unico membro della funzione applicabile è un checked operator, verrà utilizzato. Se esistono sia un regular operator che un checked operator e sono ugualmente applicabili, il checked operator sarà preferito. Se esistono sia un regular operator che un checked operator, ma il regular operator è una corrispondenza esatta mentre il checked operator non è, il compilatore preferisce l'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);
}

Un altro modo per creare il set di operatori candidati definiti dall'utente

Risoluzione dell'overload dell'operatore unario

Supponendo che regular operator corrisponda al unchecked contesto di valutazione, checked operator corrisponda al checked contesto di valutazione e che un operatore che non ha la forma checked (ad esempio, +) corrisponda a entrambi i contesti, il primo punto elenco al §12.4.4 - Risoluzione dell'overload dell'operatore Unario:

verrà sostituito con i seguenti due punti elenco:

  • L'insieme degli operatori candidati definiti dall'utente forniti da per l'operazione nel contesto selezionato/deselezionato corrente viene determinato utilizzando le regole dei candidati operatori definiti dall'utente.
  • Se il set di operatori candidati definiti dall'utente non è vuoto, questo diventa il set di operatori candidati per l'operazione. In alternativa, il set di operatori candidati definiti dall'utente forniti da X per l'operazione operator op(x)che corrisponde al contesto opposto selezionato/deselezionato viene determinato utilizzando le regole di , §12.4.6 - Operatori candidati definiti dall'utente.

Risoluzione dell'overload dell'operatore binario

Supponendo che regular operator corrisponda al unchecked contesto di valutazione, checked operator corrisponda al checked contesto di valutazione e un operatore che non ha una forma di checked (ad esempio, %) corrisponda a entrambi i contesti, il primo punto elenco in §12.4.5 - Risoluzione dell'overload dell'operatore binario:

  • Viene determinato il set di operatori candidati definiti dall'utente, forniti da X e Y, per l'operazione operator op(x,y). Il set è costituito dall'unione degli operatori candidati forniti da X e dagli operatori candidati forniti da Y, ognuno determinato usando le regole di §12.4.6 - Operatori candidati definiti dall'utente. Se X e Y sono dello stesso tipo o se X e Y derivano da un tipo di base comune, gli operatori candidati condivisi si verificano solo una volta nel set combinato.

verrà sostituito con i seguenti due punti elenco:

  • Il set di operatori candidati definiti dall'utente forniti da X e Y per l'operazione operator op(x,y), che corrispondono al contesto selezionato/deselezionato corrente, viene determinato. Il set è costituito dall'unione degli operatori candidati forniti da X e dagli operatori candidati forniti da Y, ognuno determinato usando le regole di §12.4.6 - Operatori candidati definiti dall'utente. Se X e Y sono dello stesso tipo o se X e Y derivano da un tipo di base comune, gli operatori candidati condivisi si verificano solo una volta nel set combinato.
  • Se il set di operatori candidati definiti dall'utente non è vuoto, questo diventa il set di operatori candidati per l'operazione. In caso contrario, viene determinato il set di operatori definiti dall'utente candidati forniti da X e Y per l'operazione operator op(x,y)corrispondente al contesto opposto selezionato/deselezionato. Il set è costituito dall'unione degli operatori candidati forniti da X e dagli operatori candidati forniti da Y, ognuno determinato usando le regole di §12.4.6 - Operatori candidati definiti dall'utente. Se X e Y sono dello stesso tipo o se X e Y derivano da un tipo di base comune, gli operatori candidati condivisi si verificano solo una volta nel set combinato.
Esempio 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);
}
Esempio 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
{
}
Esempio 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
{
}
Esempio 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();
}
Esempio 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();
}

Elaborazione di conversioni esplicite definite dall'utente

Supponendo che regular operator corrisponda al contesto di valutazione unchecked e checked operator al contesto di valutazione checked, il terzo punto elenco in §10.5.3 Valutazione delle conversioni definite dall'utente:

  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct in D che convertono da un tipo che include o è incluso in S a un tipo che include o è incluso in T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

verranno sostituiti con i punti elenco seguenti:

  • Individua il set di operatori di conversione espliciti, sollevati e definiti dall'utente applicabili che corrispondono al contesto selezionato/deselezionato corrente, U0. Questo set è costituito dagli operatori di conversione espliciti definiti dall'utente e potenziati (lifted) dichiarati dalle classi o dagli struct in D che corrispondono al contesto controllato/non controllato corrente e convertono da un tipo che include o è incluso da S a un tipo che include o è incluso da T.
  • Trovare il set di operatori di conversione espliciti definiti dall'utente applicabili e lifted corrispondenti al contesto checked/unchecked opposto, U1. Se U0 non è vuoto, U1 è vuoto. In caso contrario, questo set è costituito dagli operatori di conversione espliciti definiti dall'utente e rialzati dichiarati dalle classi o dagli struct in D che corrispondono al contesto checked/unchecked opposto e convertono da un tipo che comprende o è compreso da S a un tipo che comprende o è compreso da T.
  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito da operatori di U0, U1e dagli operatori di conversione impliciti definiti dall'utente e liftati dichiarati dalle classi o dagli struct in D che convertono da un tipo che include o è incluso da S a un tipo che include o è incluso da T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

Un altro modo per creare il set di operatori candidati definiti dall'utente

Risoluzione dell'overload dell'operatore unario

Il primo punto elenco nella sezione §12.4.4 verrà modificato come segue (le aggiunte sono in grassetto).

  • Il set di operatori candidati definiti dall'utente forniti da X per l'operazione operator op(x) viene determinato usando le regole della sezione "Operatori definiti dall'utente candidati" di seguito. Se nel set vi è almeno un operatore in versione selezionata, tutti gli operatori in versione regolare vengono eliminati dal set.

La sezione §12.8.20 verrà modificata in modo da riflettere l'effetto che il contesto attivato/disattivato ha sulla risoluzione del sovraccarico dell'operatore unario.

Risoluzione dell'overload dell'operatore binario

Il primo punto elenco nella sezione §12.4.5 verrà modificato come segue (le aggiunte sono in grassetto).

  • Viene determinato il set di operatori candidati definiti dall'utente, forniti da X e Y, per l'operazione operator op(x,y). Il set è costituito dall'unione degli operatori candidati forniti da X e dagli operatori candidati forniti da Y, ognuno determinato usando le regole della sezione "Operatori definiti dall'utente candidati" di seguito. Se X e Y sono dello stesso tipo o se X e Y derivano da un tipo di base comune, gli operatori candidati condivisi si verificano solo una volta nel set combinato. Se nel set vi è almeno un operatore in versione selezionata, tutti gli operatori in versione regolare vengono eliminati dal set.

Gli operatori checked e unchecked, nella sezione §12.8.20, verranno modificati in modo da riflettere l'effetto che il contesto checked/unchecked ha sulla risoluzione del sovraccarico dell'operatore binario.

Operatori candidati definiti dall'utente

La sezione 12.4.6 - Operatori candidati definiti dall'utente verrà modificata come indicato di seguito (le aggiunte sono in grassetto).

Dato un tipo T e un'operazione operator op(A), dove op è un operatore sovraccaricabile e A è una lista di argomenti, il set di operatori candidati definiti dall'utente forniti da T per operator op(A) viene determinato come segue:

  • Determinare il tipo T0. Se T è un tipo nullable, T0 è il tipo sottostante; in caso contrario, T0 è uguale a T.
  • Per tutte le dichiarazioni operator opnelle forme controllate e regolari nel contesto di valutazione checked e solo nella forma regolare nel contesto di valutazione unchecked in T0 e in tutte le forme sollevate di tali operatori, se almeno un operatore è applicabile (§12.6.4.2) rispetto all'elenco di argomenti A, il set di operatori candidati comprende tutti gli operatori applicabili in T0.
  • In caso contrario, se T0 è object, l'insieme di operatori candidati è vuoto.
  • In caso contrario, il set di operatori candidati forniti da T0 è il set di operatori candidati forniti dalla classe base diretta di T0o dalla classe base effettiva di T0 se T0 è un parametro di tipo.

Verrà applicato un filtro simile durante la determinazione del set di operatori candidati nelle interfacce https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

La sezione §12.8.20 verrà modificata in modo da riflettere l'effetto che il contesto abilitato/disabilitato ha sulla risoluzione dell'overload degli operatori unari e binari.

Esempio 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);
}
Esempio 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
{
}
Esempio 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
{
}
Esempio 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();
}
Esempio 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();
}

Elaborazione di conversioni esplicite definite dall'utente

Terzo elemento dell'elenco in §10.5.5:

  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct in D che convertono da un tipo che include o è incluso in S a un tipo che include o è incluso in T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

verranno sostituiti con i punti elenco seguenti:

  • Trovare il set di operatori di conversione espliciti e definiti dall'utente applicabili, U0. Questo set è costituito dagli operatori di conversione espliciti definiti dall'utente e liftati dichiarati dalle classi o dalle strutture in Dnelle forme controllate e regolari nel contesto di valutazione checked e solo nella forma regolare nel contesto di valutazione unchecked e convertono da un tipo che comprende o è compreso da S a un tipo che comprende o è compreso da T.
  • Se U0 contiene almeno un operatore nella forma selezionata, tutti gli operatori in forma regolare vengono rimossi dal set.
  • Individua l'insieme degli operatori di conversione definiti dall'utente e sollevati applicabili, U. Questo set è costituito da operatori da U0e dagli operatori di conversione impliciti definiti dall'utente e sollevati dichiarati dalle classi o dagli struct in D che convertono da un tipo che comprende o è compreso da S a un tipo che comprende o è compreso da T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

Gli operatori checked e unchecked nella sezione §12.8.20 verranno modificati per riflettere l'effetto che il contesto checked/unchecked ha sull'elaborazione delle conversioni esplicite definite dall'utente.

Contesto controllato e non controllato all'interno di un checked operator

Il compilatore potrebbe trattare il contesto predefinito di un checked operator come controllato. Lo sviluppatore dovrà usare in modo esplicito unchecked se parte dell'algoritmo non deve partecipare al checked context. Tuttavia, questo potrebbe non funzionare correttamente in futuro se inizieremo a consentire i token checked/unchecked come modificatori sugli operatori per impostare il contesto nel codice. Il modificatore e la parola chiave potrebbero contraddersi tra loro. Inoltre, non sarebbe possibile eseguire la stessa operazione (considerare il contesto predefinito come deselezionato) per un regular operator perché si tratta di una modifica che causa un'interruzione.

Domande non risolte

Il linguaggio dovrebbe consentire modificatori checked e unchecked sui metodi (ad esempio static checked void M())? In questo modo è possibile rimuovere i livelli di annidamento per i metodi che lo richiedono.

Divisione verificata negli alberi delle espressioni Linq

Non esiste alcun metodo fabbrica per creare un nodo di divisione verificato e non esiste alcun membro ExpressionType.DivideChecked. È comunque possibile usare il metodo factory seguente per creare un nodo di divisione regolare con MethodInfo che punta al metodo op_CheckedDivision. I consumatori dovranno controllare il nome per inferire il contesto.

public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);

Si noti che, anche se sezione di sezione del paragrafo 12.8.20 elenca / operatore come uno degli operatori interessati dal contesto di valutazione selezionato/non controllato, IL non dispone di un codice operativo speciale per eseguire la divisione controllata. Il compilatore usa sempre il factory method indipendentemente dal contesto.

Proposta: La divisione definita dall'utente validata non sarà supportata negli Expression Trees di Linq.

(Risolto) È consigliabile supportare operatori di conversione controllati impliciti?

In generale, gli operatori di conversione implicita non dovrebbero lanciare eccezioni.

Proposta: No.

risoluzione : approvata - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions

Riunioni di progettazione

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