Bagikan melalui


Memeriksa operator yang ditentukan pengguna

Nota

Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.

Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut dijelaskan dalam catatan rapat terkait desain bahasa (LDM) .

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

Masalah juara: https://github.com/dotnet/csharplang/issues/4665

Ringkasan

C# harus mendukung penentuan varian checked dari operator yang didefinisikan pengguna berikut sehingga pengguna dapat mengaktifkan atau menonaktifkan perilaku luapan yang sesuai.

Motivasi

Tidak ada cara bagi pengguna untuk mendeklarasikan tipe dan mendukung versi operator yang sudah dicek dan belum dicek. Ini akan menyulitkan penerapan berbagai algoritma untuk menggunakan antarmuka generic math yang diusulkan dan diekspos oleh tim pustaka. Demikian juga, ini membuatnya tidak mungkin untuk mengekspos jenis seperti Int128 atau UInt128 tanpa bahasa secara bersamaan menyertakan dukungannya sendiri untuk menghindari perubahan yang merusak.

Desain terperinci

Sintaksis

Tata bahasa pada operator (§15,10) akan disesuaikan untuk mengizinkan kata kunci checked setelah kata kunci operator tepat sebelum token operator:

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

Misalnya:

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

Untuk kejelasan di bawah ini, operator dengan kata kunci checked disebut sebagai checked operator dan operator tanpa kata kunci checked disebut sebagai . Ketentuan ini tidak berlaku untuk operator yang tidak memiliki formulir checked.

Semantik

checked operator yang ditentukan pengguna diharapkan melemparkan pengecualian ketika hasil operasi terlalu besar untuk diwakili dalam jenis tujuan. Apa artinya terlalu besar sebenarnya tergantung pada sifat jenis tujuan dan tidak ditentukan oleh bahasa. Biasanya pengecualian yang dilemparkan adalah System.OverflowException, tetapi bahasa tidak memiliki persyaratan khusus mengenai hal ini.

regular operator yang ditentukan pengguna diharapkan tidak melemparkan pengecualian ketika hasil operasi terlalu besar untuk diwakili dalam jenis tujuan. Sebaliknya, diharapkan untuk mengembalikan instance yang mewakili hasil yang terpotong. Apa artinya terlalu besar dan dipotong sebenarnya tergantung pada sifat jenis tujuan dan tidak ditentukan oleh bahasa.

Semua operator yang ditentukan pengguna yang ada di luar sana yang akan memiliki formulir checked yang didukung termasuk dalam kategori regular operators. Dipahami bahwa banyak dari mereka mungkin tidak mengikuti semantik yang ditentukan di atas, tetapi untuk tujuan analisis semantik, kompilator akan mengasumsikan bahwa mereka melakukannya.

Konteks dicentang vs. tidak dicentang dalam checked operator

Konteks yang dicentang/tidak dicentang dalam isi checked operator tidak terpengaruh oleh keberadaan kata kunci checked. Dengan kata lain, konteksnya sama dengan tepat di awal deklarasi operator. Pengembang harus secara eksplisit mengalihkan konteks jika bagian dari algoritma mereka tidak dapat mengandalkan konteks default.

Nama dalam metadata

Bagian "I.10.3.1 Operator unary" dari ECMA-335 akan disesuaikan untuk menyertakan op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation sebagai nama metode yang mengimplementasikan operator unary yang diperiksa ++, --, dan -.

Bagian "Operator Biner I.10.3.2" ECMA-335 akan disesuaikan untuk menyertakan op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision sebagai nama untuk metode yang menerapkan operator biner +, -, *, dan / yang diperiksa.

Bagian "Operator konversi I.10.3.3" ECMA-335 akan disesuaikan untuk menyertakan op_CheckedExplicit sebagai nama untuk metode yang menerapkan operator konversi eksplisit yang diperiksa.

Operator Unary

Unary checked operators mengikuti aturan dari §15.10.2.

Selain itu, deklarasi checked operator memerlukan deklarasi berpasangan dari regular operator (tipe pengembalian juga harus sesuai). Jika tidak, akan terjadi kesalahan waktu kompilasi.

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

Operator biner

Biner checked operators mengikuti aturan dari §15.10.3.

Selain itu, deklarasi checked operator memerlukan deklarasi berpasangan dari regular operator (tipe pengembalian juga harus sesuai). Jika tidak, akan terjadi kesalahan waktu kompilasi.

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

Operator kandidat yang ditentukan pengguna

Bagian Operator pengguna Kandidat (§12.4.6) akan disesuaikan sebagai berikut (penambahan/perubahan dalam huruf tebal).

Mengingat jenis T dan operasi operator op(A), di mana op adalah operator yang dapat kelebihan beban dan A adalah daftar argumen, kumpulan operator yang ditentukan pengguna kandidat yang disediakan oleh T untuk operator op(A) ditentukan sebagai berikut:

  • Tentukan jenis T0. Jika T adalah jenis nullable, T0 adalah jenis dasarnya, jika bukan, T0 sama dengan T.
  • Temukan kumpulan operator yang ditentukan pengguna, U. Set ini terdiri dari:
    • Dalam konteks evaluasi unchecked, semua deklarasi operator op reguler dalam T0.
    • Dalam konteks evaluasi checked, semua deklarasi operator op diperiksa dan teratur dalam T0 kecuali deklarasi reguler yang memiliki deklarasi checked operator pencocokan pasangan.
  • Untuk semua deklarasi operator op dalam U dan semua bentuk terangkat dari operator tersebut, jika setidaknya satu operator dapat diterapkan (§12.4.6 - Anggota fungsi yang dapat diterapkan) terhadap daftar argumen A, maka kumpulan operator kandidat mencakup seluruh operator yang dapat diterapkan di T0.
  • Jika tidak, jika T0 adalah object, maka himpunan operator kandidat kosong.
  • Jika tidak, kumpulan operator kandidat yang disediakan oleh T0 adalah kumpulan operator kandidat yang disediakan oleh kelas dasar langsung T0, atau kelas dasar efektif T0 jika T0 adalah parameter tipe.

Aturan serupa akan diterapkan saat menentukan set operator kandidat dalam antarmuka https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Bagian §12.8.20 akan disesuaikan untuk mencerminkan efek yang dimiliki konteks yang dicentang/tidak dicentang pada resolusi kelebihan beban operator unary dan biner.

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

Contoh #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
{
}

Contoh #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
{
}

Operator konversi

Konversi checked operators mengikuti aturan dari §15.10.4.

Namun, deklarasi checked operator memerlukan deklarasi secara berpasangan dari regular operator. Jika tidak, akan terjadi kesalahan waktu kompilasi.

Paragraf berikut

Tanda tangan operator konversi terdiri dari jenis sumber dan jenis target. (Ini adalah satu-satunya bentuk anggota yang jenis pengembaliannya menjadi bagian dari tanda tangan.) Klasifikasi implisit atau eksplisit operator konversi bukan bagian dari tanda tangan operator. Dengan demikian, kelas atau struktur tidak dapat mendeklarasikan operator konversi implisit dan eksplisit dengan jenis sumber dan target yang sama.

akan disesuaikan untuk memungkinkan tipe mendeklarasikan bentuk konversi eksplisit yang dicek dan biasa dengan tipe sumber dan target yang sama. Jenis tidak akan diizinkan untuk mendeklarasikan operator konversi implisit dan operator konversi eksplisit yang dicek dengan jenis sumber dan target yang sama.

Pemrosesan konversi eksplisit yang ditentukan pengguna

Poin ketiga dalam §10.5.5:

  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator konversi implisit atau eksplisit yang ditentukan pengguna dan diangkat, yang dideklarasikan oleh kelas atau struktur dalam D, yang mengonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

akan diganti dengan poin-poin utama berikut:

  • Temukan set operator konversi, U0. Set ini terdiri dari:
    • Dalam konteks evaluasi unchecked, operator konversi implisit atau eksplisit reguler yang didefinisikan oleh pengguna yang dideklarasikan oleh kelas atau struktur dalam D.
    • Dalam konteks evaluasi checked, operator konversi eksplisit implisit atau reguler yang ditentukan pengguna yang dideklarasikan oleh kelas atau struktur dalam D kecuali operator konversi eksplisit reguler yang memiliki pencocokan pasangan checked operator deklarasi dalam jenis deklarasi yang sama.
  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator konversi implisit atau eksplisit yang ditentukan pengguna dan diangkat dalam U0 yang mengonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

Operator checked dan unchecked di bagian §11.8.20 akan disesuaikan untuk mencerminkan efek yang dimiliki konteks checked/unchecked pada pemrosesan konversi eksplisit yang didefinisikan oleh pengguna.

Mengimplementasikan operator

checked operator tidak menerapkan regular operator dan sebaliknya.

Pohon Ekspresi Linq

Checked operators akan didukung dalam Expression Trees Linq. Simpul UnaryExpression/BinaryExpression akan dibuat dengan padanan MethodInfoyang sesuai. Metode pabrik berikut akan digunakan:

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

Perhatikan, bahwa C# tidak mendukung penugasan di pohon ekspresi, oleh karena itu kenaikan/penurunan yang diperiksa tidak akan didukung juga.

Tidak ada metode pabrik untuk pembagian yang diperiksa. Ada pertanyaan terbuka mengenai analisis pembagian pada Struktur Ekspresi Linq ini - .

Dinamis

Kami akan menyelidiki biaya penambahan dukungan untuk operator yang diperiksa dalam pemanggilan dinamis di CoreCLR dan mengejar implementasi jika biayanya tidak terlalu tinggi. Ini adalah kutipan dari https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.

Kekurangan

Ini menambahkan kompleksitas ke bahasa dan memungkinkan pengguna untuk memperkenalkan lebih banyak jenis perubahan signifikan pada tipe data mereka.

Alternatif

Antarmuka matematika generik yang direncanakan pustaka untuk diekspos dapat mengekspos metode bernama (seperti AddChecked). Kelemahan utamanya adalah bahwa ini kurang dapat dibaca/dipertahankan dan tidak mendapatkan manfaat dari aturan prioritas bahasa di sekitar operator.

Bagian ini mencantumkan alternatif yang dibahas, tetapi tidak diimplementasikan

Penempatan kata kunci checked

Atau kata kunci checked dapat dipindahkan ke tempat tepat sebelum kata kunci 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) {...}

Atau dapat dipindahkan ke kumpulan pengubah operator:

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

kata kunci unchecked

Ada saran untuk mendukung kata kunci unchecked pada posisi yang sama dengan kata kunci checked dengan kemungkinan arti berikut:

  • Hanya untuk mencerminkan secara eksplisit sifat keteraturan operator tersebut, atau
  • Mungkin untuk menunjuk varian operator yang berbeda yang seharusnya digunakan dalam konteks unchecked. Bahasa ini dapat mendukung op_Addition, op_CheckedAddition, dan op_UncheckedAddition untuk membantu membatasi jumlah perubahan yang mengganggu. Ini menambahkan lapisan kompleksitas lain yang kemungkinan tidak diperlukan di sebagian besar kode.

Nama operator di ECMA-335

Atau nama operator dapat op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, dengan Checked di akhir. Namun, sepertinya sudah ada pola yang terbentuk untuk mengakhiri nama dengan kata operator. Misalnya, terdapat operator op_UnsignedRightShift daripada operator op_RightShiftUnsigned.

Checked operators tidak dapat diaplikasikan dalam konteks unchecked

Kompilator, ketika melakukan pencarian anggota untuk menemukan operator yang didefinisikan pengguna dalam konteks unchecked, dapat mengabaikan checked operators. Jika metadata ditemukan yang hanya menentukan checked operator, maka kesalahan kompilasi akan terjadi.

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

Aturan pencarian operator yang lebih rumit dan resolusi kelebihan beban dalam konteks checked

Pengkompilasi, saat melakukan pencarian anggota untuk menemukan operator spesifik pengguna kandidat dalam konteks checked, juga akan mempertimbangkan operator yang berlaku yang diakhiri dengan Checked. Artinya, jika pengkompilasi mencoba menemukan anggota fungsi yang berlaku untuk operator penambahan biner, ia akan mencari op_Addition dan op_AdditionChecked. Jika satu-satunya anggota fungsi yang sesuai adalah checked operator, maka itu akan digunakan. Jika regular operator dan checked operator ada dan sama-sama berlaku, checked operator akan lebih disukai. Jika regular operator dan checked operator ada tetapi regular operator sama persis sementara checked operator tidak, kompilator akan lebih memilih 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);
}

Cara lain lagi untuk membangun kumpulan operator yang mungkin ditentukan pengguna.

Resolusi kelebihan beban operator unary

Dengan asumsi bahwa regular operator sesuai dengan konteks evaluasi unchecked, checked operator cocok dengan konteks evaluasi checked, dan operator yang tidak memiliki bentuk checked (misalnya, +) cocok dengan keduanya, maka butir pertama pada §12.4.4 - Resolusi kelebihan beban operator unary:

akan diganti dengan dua poin berikut ini:

  • Kumpulan operator kandidat yang ditentukan pengguna yang disediakan oleh X untuk operasi operator op(x)yang cocok dengan konteks aktif/non-aktif saat ini ditentukan menggunakan aturan kandidat operator yang ditentukan pengguna .
  • Jika koleksi operator yang ditentukan pengguna tidak kosong, maka ini menjadi kumpulan operator kandidat untuk operasi tersebut. Jika tidak, kumpulan operator kandidat yang ditentukan pengguna yang disediakan oleh X untuk operasi operator op(x)yang mencocokkan konteks yang diperiksa/tidak dicentang yang berlawanan ditentukan dengan menggunakan aturan §12.4.6 - Operator kandidat yang ditentukan pengguna.

Resolusi kelebihan beban operator biner

Dengan asumsi bahwa regular operator cocok dengan konteks evaluasi unchecked, checked operator cocok dengan konteks evaluasi checked, dan operator yang tidak memiliki bentuk checked (misalnya, %) cocok dengan salah satu dari konteks tersebut, butir pertama dalam §12.4.5 - Resolusi kelebihan beban operator biner:

  • Kumpulan operator yang ditentukan pengguna kandidat yang disediakan oleh X dan Y untuk operasi operator op(x,y) ditentukan. Set ini terdiri dari penyatuan operator kandidat yang disediakan oleh X dan operator kandidat yang disediakan oleh Y, masing-masing ditentukan menggunakan aturan §12.4.6 - Operator kandidat yang ditentukan pengguna. Jika X dan Y adalah jenis yang sama, atau jika X dan Y berasal dari jenis dasar umum, operator kandidat bersama hanya terjadi dalam kumpulan gabungan sekali.

akan diganti dengan dua poin berikut ini:

  • Kumpulan operator kandidat yang didefinisikan pengguna yang disediakan oleh X dan Y untuk operasi operator op(x,y)yang cocok dengan konteks dicentang/tidak dicentang saat ini ditentukan. Set ini terdiri dari penyatuan operator kandidat yang disediakan oleh X dan operator kandidat yang disediakan oleh Y, masing-masing ditentukan menggunakan aturan §12.4.6 - Operator kandidat yang ditentukan pengguna. Jika X dan Y adalah jenis yang sama, atau jika X dan Y berasal dari jenis dasar umum, operator kandidat bersama hanya terjadi dalam kumpulan gabungan sekali.
  • Jika koleksi operator yang ditentukan pengguna tidak kosong, maka ini menjadi kumpulan operator kandidat untuk operasi tersebut. Jika tidak, kumpulan operator kandidat yang ditentukan pengguna dan disediakan oleh X dan Y untuk operasi operator op(x,y)yang cocok dengan konteks diperiksa/tidak dicentang yang berlawanan ditentukan. Set ini terdiri dari penyatuan operator kandidat yang disediakan oleh X dan operator kandidat yang disediakan oleh Y, masing-masing ditentukan menggunakan aturan §12.4.6 - Operator kandidat yang ditentukan pengguna. Jika X dan Y adalah jenis yang sama, atau jika X dan Y berasal dari jenis dasar umum, operator kandidat bersama hanya terjadi dalam kumpulan gabungan sekali.
Contoh #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);
}
Contoh #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
{
}
Contoh #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
{
}
Contoh #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();
}
Contoh #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();
}

Pemrosesan konversi eksplisit yang ditentukan pengguna

Dengan asumsi bahwa regular operator cocok dengan konteks evaluasi unchecked dan checked operator cocok dengan konteks evaluasi checked, poin ketiga dalam §10.5.3 Evaluasi konversi yang ditentukan pengguna:

  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator konversi implisit atau eksplisit yang ditentukan pengguna dan diangkat, yang dideklarasikan oleh kelas atau struktur dalam D, yang mengonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

akan diganti dengan poin-poin utama berikut:

  • Temukan kumpulan operator konversi eksplisit yang ditentukan pengguna dan diangkat yang berlaku cocok dengan konteks yang dicentang/tidak dicentang saat ini, U0. Set ini terdiri dari operator konversi eksplisit yang ditentukan pengguna dan diangkat yang dideklarasikan oleh kelas atau struktur di D yang cocok dengan konteks yang diperiksa/tidak dicentang saat ini dan dikonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T.
  • Temukan kumpulan operator konversi eksplisit yang ditentukan pengguna dan diangkat yang berlaku cocok dengan konteks yang diperiksa/tidak dicentang yang berlawanan, U1. Jika U0 tidak kosong, U1 kosong. Jika tidak, set ini terdiri dari operator konversi eksplisit yang ditentukan pengguna dan diangkat yang dideklarasikan oleh kelas atau struktur di D yang cocok dengan konteks yang diperiksa/tidak dicentang dan dikonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T.
  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator dari U0, U1, serta operator konversi implisit yang didefinisikan oleh pengguna dan dinaikkan yang dideklarasikan oleh kelas atau struktur dalam D yang melakukan konversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

Namun, lagi-lagi ada cara lain untuk membangun serangkaian operator yang ditentukan untuk kandidat pengguna.

Resolusi kelebihan beban operator unary

Poin pertama di bagian §12.4.4 akan disesuaikan sebagai berikut (penambahan dalam huruf tebal).

  • Kumpulan operator yang ditentukan pengguna kandidat yang disediakan oleh X untuk operasi operator op(x) ditentukan menggunakan aturan bagian "Operator yang ditentukan pengguna kandidat" di bawah ini. Jika set berisi setidaknya satu operator dalam formulir yang dicentang, semua operator dalam bentuk reguler akan dihapus dari set.

Bagian §12.8.20 akan disesuaikan untuk mencerminkan efek yang dimiliki konteks yang diperiksa/tidak dicentang pada resolusi kelebihan beban operator unary.

Resolusi kelebihan beban operator biner

Poin pertama di bagian §12.4.5 akan disesuaikan sebagai berikut (penambahan dalam "huruf tebal").

  • Kumpulan operator yang ditentukan pengguna kandidat yang disediakan oleh X dan Y untuk operasi operator op(x,y) ditentukan. Set ini terdiri dari gabungan operator kandidat yang diberikan oleh X dan operator kandidat yang diberikan oleh Y, masing-masing ditentukan berdasarkan aturan dalam bagian "Kandidat operator yang ditentukan pengguna" di bawah ini. Jika X dan Y adalah jenis yang sama, atau jika X dan Y berasal dari jenis dasar umum, operator kandidat bersama hanya terjadi dalam kumpulan gabungan sekali. Jika set berisi setidaknya satu operator dalam formulir yang dicentang, semua operator dalam bentuk reguler akan dihapus dari set.

Operator yang diperiksa dan tidak dicentang §12.8.20 akan disesuaikan untuk mencerminkan efek yang dimiliki konteks yang dicentang/tidak dicentang pada resolusi kelebihan beban operator biner.

Operator kandidat yang ditentukan pengguna

§12.4.6 - Operator yang didefinisikan pengguna sebagai kandidat bagian akan disesuaikan sebagai berikut ini (penambahan dalam huruf tebal).

Mengingat jenis T dan operasi operator op(A), di mana op adalah operator yang dapat kelebihan beban dan A adalah daftar argumen, kumpulan operator yang ditentukan pengguna kandidat yang disediakan oleh T untuk operator op(A) ditentukan sebagai berikut:

  • Tentukan jenis T0. Jika T adalah jenis nullable, T0 adalah jenis dasarnya, jika bukan, T0 sama dengan T.
  • Untuk semua deklarasi operator opdalam bentuk yang telah diperiksa dan teratur dalam konteks evaluasi checked dan hanya dalam bentuk reguler dalam konteks evaluasi unchecked dalam T0 dan semua bentuk pengangkatan dari operator tersebut, jika setidaknya satu operator berlaku (§12.6.4.2) sehubungan dengan daftar argumen A, maka kumpulan operator kandidat yang terdiri dari semua operator yang berlaku dalam T0.
  • Jika tidak, jika T0 adalah object, maka himpunan operator kandidat kosong.
  • Jika tidak, kumpulan operator kandidat yang disediakan oleh T0 adalah kumpulan operator kandidat yang disediakan oleh kelas dasar langsung T0, atau kelas dasar efektif T0 jika T0 adalah parameter tipe.

Pemfilteran serupa akan diterapkan saat menentukan set operator kandidat dalam antarmuka https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Bagian §12.8.20 akan disesuaikan untuk mencerminkan efek yang dimiliki konteks yang diperiksa/tidak dicentang pada resolusi kelebihan beban operator unary dan biner.

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

Pemrosesan konversi eksplisit yang ditentukan pengguna

Poin ketiga dalam §10.5.5:

  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator konversi implisit atau eksplisit yang ditentukan pengguna dan diangkat, yang dideklarasikan oleh kelas atau struktur dalam D, yang mengonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

akan diganti dengan poin-poin utama berikut:

  • Temukan kumpulan operator konversi eksplisit yang ditentukan pengguna dan diarahkan yang relevan, U0. Set ini terdiri dari operator konversi eksplisit yang ditentukan pengguna dan diangkat yang dideklarasikan oleh kelas atau struktur dalam Ddalam bentuk yang diperiksa dan teratur dalam konteks evaluasi checked dan hanya dalam bentuk teratur dalam konteks evaluasi unchecked, serta konversi dari tipe yang mencakup atau dicakup oleh S ke tipe yang mencakup atau dicakup oleh T.
  • Jika U0 berisi setidaknya satu operator dalam formulir yang dicentang, semua operator dalam bentuk reguler akan dihapus dari set.
  • Temukan kumpulan operator konversi yang ditentukan pengguna dan diangkat yang berlaku, U. Set ini terdiri dari operator-operator dari U0, dan operator konversi implisit yang ditentukan oleh pengguna dan dinaikkan yang dideklarasikan dalam kelas atau struktur di D yang mengkonversi dari jenis yang mencakup atau dicakup oleh S ke jenis yang mencakup atau dicakup oleh T. Jika U kosong, konversi tidak terdefinisi dan terjadi kesalahan waktu kompilasi.

Operator checked dan unchecked pada bagian §12.8.20 akan disesuaikan untuk mencerminkan efek konteks checked/unchecked pada pemrosesan konversi eksplisit yang didefinisikan oleh pengguna.

Konteks dicentang vs. tidak dicentang dalam checked operator

Pengkompilasi dapat memperlakukan konteks default checked operator seperti yang diperiksa. Pengembang harus secara eksplisit menggunakan unchecked jika bagian dari algoritma mereka tidak boleh berpartisipasi dalam checked context. Namun, ini mungkin tidak berfungsi dengan baik di masa depan jika kita mulai mengizinkan token checked/unchecked sebagai pengubah pada operator untuk mengatur konteks dalam isi. Pengubah dan kata kunci dapat saling bertentangan. Selain itu, kami tidak akan dapat melakukan hal yang sama (memperlakukan konteks default sebagai tidak dicentang) untuk regular operator karena itu akan menjadi perubahan yang mengganggu kompatibilitas.

Pertanyaan yang belum terselesaikan

Haruskah bahasa mengizinkan pengubah checked dan unchecked pada metode (misalnya static checked void M())? Ini akan memungkinkan penghapusan tingkat penganakan untuk metode yang membutuhkannya.

Memeriksa pembagian dalam Pohon Ekspresi LINQ

Tidak ada metode factory untuk membuat simpul pembagian yang diperiksa dan tidak ada anggota ExpressionType.DivideChecked. Kita masih dapat menggunakan metode pabrik berikut untuk membuat simpul pembagi reguler dengan MethodInfo menunjuk ke metode op_CheckedDivision. Konsumen harus memeriksa nama untuk menyimpulkan konteks.

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

Perhatikan, meskipun bagian §12.8.20 mencantumkan operator / sebagai salah satu operator yang terpengaruh oleh konteks evaluasi yang dicentang/tidak dicentang, IL tidak memiliki kode op khusus untuk melakukan pembagian yang diperiksa. Kompilator selalu menggunakan metode pabrik tanpa memperdulikan konteks saat ini.

Proposal: Pembagian yang ditentukan pengguna dan diperiksa tidak akan didukung dalam Pohon Ekspresi Linq.

(Diselesaikan) Haruskah kita mendukung operator konversi implisit yang diperiksa?

Secara umum, operator konversi implisit tidak diharapkan untuk melemparkan.

Proposal : Tidak.

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

Rapat desain

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