Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Edisi unggulan: https://github.com/dotnet/csharplang/issues/9499
RINGKASAN
Izinkan kelas dideklarasikan closed. Ini mencegah kelas turunan langsung dideklarasikan dalam rakitan yang berbeda:
// Assembly 1
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;
// Assembly 2
public record class Locked : GateState; // ERROR - 'GateState' is a closed class
Karena semua kelas turunan dinyatakan dalam rakitan kelas tertutup, ekspresi konsumsi switch yang mencakup semuanya dapat disimpulkan untuk "menghabiskan" kelas tertutup - tidak perlu memberikan kasus default untuk menghindari peringatan.
// Assembly 3
GateState state = ...;
string description = state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open"
// No warning about missing cases
};
Motivasi
Banyak jenis kelas tidak dimaksudkan untuk diperluas oleh siapa pun kecuali penulis mereka, tetapi bahasa tidak menyediakan cara untuk mengekspresikan niat itu, apalagi menjaga terhadap hal itu terjadi. Untuk konsumen kelas ini berarti bahwa tidak ada serangkaian kelas turunan yang akan dianggap "menghabiskan" kelas dasar, dan ekspresi pengalihan perlu menyertakan kasus catch-all untuk menghindari peringatan.
Kelas tertutup menyediakan cara untuk menunjukkan bahwa sekumpulan kelas turunan selesai, dan memungkinkan penggunaan kode untuk mengandalkan itu untuk kelelahan dalam ekspresi pengalihan.
Desain terperinci
Syntax
Izinkan closed sebagai pengubah pada kelas. Kelas closed secara implisit abstrak. Dengan demikian, tidak dapat juga memiliki sealed atau static pengubah.
Ini adalah kesalahan untuk secara eksplisit menggunakan abstract pengubah pada closed kelas.
Kelas yang berasal dari kelas tertutup tidak ditutup sendiri kecuali dinyatakan secara eksplisit.
Pembatasan rakitan yang sama
Jika kelas dalam satu rakitan dinyatakan closed maka itu adalah kesalahan untuk langsung berasal darinya di rakitan lain:
// Assembly 1
public closed class CC { ... }
public class CO : CC { ... } // Ok, same assembly
// Assembly 2
public class C1 : CC { ... } // Error, 'CC' is closed and in a different assembly
public class C2 : CO { ... } // Ok, 'CO' is not closed
Pembatasan yang sama berlaku untuk modul. Subjenis jenis closed harus terletak dalam modul yang sama dengan jenis dasar.
Batasan parameter jenis
Jika kelas generik secara langsung berasal dari kelas tertutup, maka semua parameter jenisnya harus digunakan dalam spesifikasi kelas dasar:
closed class C<T> { ... }
class D1<U> : C<U> { ... } // Ok, 'U' is used in base class
class D2<V> : C<V[]> { ... } // Ok, 'V' is used in base class
class D3<W> : C<int> { ... } // Error, 'W' is not used in base class
Aturan ini untuk memastikan bahwa ada satu instansiasi generik dari jenis turunan yang "habis" instans generik tertentu dari jenis dasar tertutup.
Catatan: Aturan ini mungkin tidak cukup jika kami mengizinkan antarmuka tertutup di beberapa titik, karena a) kelas dapat mengimplementasikan beberapa instansiasi generik dari antarmuka yang sama, dan b) parameter jenis antarmuka dapat berdampingan atau kontravarian. Pada titik seperti itu kita perlu menyempurnakan aturan untuk terus memastikan bahwa hanya ada satu instansiasi generik dari jenis turunan tertentu per instans generik dari jenis dasar tertutup.
Kelelahan dalam sakelar
switch Ekspresi yang menangani semua keturunan langsung dari kelas tertutup akan dianggap telah kelelahan kelas tersebut. Itu berarti bahwa beberapa peringatan non-kelelahan tidak akan lagi diberikan:
CC cc = ...;
_ = cc switch
{
CO co => ...,
// No warning about non-exhaustive switch
};
Di sisi lain ini juga berarti bahwa itu bisa menjadi kesalahan bagi kelas dasar tertutup terjadi sebagai kasus setelah semua turunan langsungnya:
_ = cc switch
{
CO co => ...,
CC cc => ..., // Error, case cannot be reached
};
Catatan: Mungkin tidak ada kelas turunan yang valid untuk instansiasi generik tertentu dari kelas dasar tertutup. Sakelar lengkap hanya perlu menentukan kasus untuk jenis turunan yang sebenarnya mungkin.
Contohnya:
closed class C<T> { ... }
class D1<U> : C<U> { ... }
class D2<V> : C<V[]> { ... }
Misalnya C<string>, tidak ada instansiasi yang sesuai dari D2<...>, dan tidak ada kasus yang D2<...> perlu diberikan dalam sakelar:
C<string> cs = ...;
_ = cs switch
{
D1<string> d1 => ...,
// No need for a 'D2<...>' case - no instantiation corresponds to 'C<string>'
}
Kelelahan ketika subjenis tidak dapat digunakan
Jika subjenis tidak valid di situs penggunaan tertentu, karena pelanggaran batasan, pelanggaran aksesibilitas, atau alasan lain, maka, tidak mungkin untuk menghabiskan sakelar melalui subjenis.
closed class C;
class D1 : C;
class Container
{
protected class D2 : C;
}
class Program
{
int M(C c)
=> c switch
{
D1 => 1,
// warning: switch is non-exhaustive. Pattern 'C' is not handled.
};
}
Ini juga berlaku ketika subjenis generik tidak dapat diucapkan, dan penerapannya dapat bergantung pada penggantian argumen jenis akhir.
closed class C<T> { ... }
class D1<U> : C<U> { ... }
class D2<V> : C<V[]> { ... }
class Program
{
int M<X>(C<X> c)
=> c switch
{
D1<X> => 1,
// warning: switch is non-exhaustive. Pattern 'C' is not handled.
};
}
Batasan subjenis tidak memengaruhi kelelahan
Bahasa ini tidak menyempurnakan penentuan apakah subjenis dimungkinkan berdasarkan batasan pada parameter jenis dalam jenis dasar dan definisi subjenis.
closed class C<T>;
class D1<U1> : C<U1>;
class D2<U2> : C<U2> where U2 : struct;
class Program
{
int M1<X>(C<X> c) where X : class
{
// warning: switch is not exhaustive. Pattern 'C<X>' is not handled.
return c switch
{
D1<X> => 1,
};
}
int M2<X>(C<X> c) where X : class
{
return c switch
{
D1<X> => 1,
C<X> => 2, // ok
};
}
}
Misalnya, ekspresi switch di atas, tidak menganalisis konstruksi D2<X>dengan cukup tepat , untuk menyadari bahwa semua kemungkinan X melanggar batasan U2. Oleh karena itu, ia mengasumsikan bahwa beberapa D2<X> dimungkinkan, dan meminta pengguna untuk menanganinya dengan menghabiskan jenis dasar.
Kelelahan ketika tidak ada subjenis
Ketika kelas tertutup tidak memiliki subjenis, sakelar kosong di atasnya tidak dianggap lengkap.
Komentar: Ini diasumsikan sebagai "status perantara" dalam kode normal. Penulis kemungkinan besar akan membuat perubahan untuk mendeklarasikan subjenis dalam skenario ini. Perilaku ini berjumlah "quirk"--meskipun "semua 0 subjenis ditangani", bahasa masih meminta pengguna untuk menangani jenis dasar.
closed class C;
class Program
{
int M1(C c)
// warning: switch is not exhaustive.
=> c switch
{
};
int M2(C c)
=> c switch
{
C => 1, // ok
};
}
Kelelahan parameter jenis yang dibatasi untuk jenis tertutup
Parameter jenis yang dibatasi untuk kelas tertutup diperlakukan sama seperti kelas tertutup untuk tujuan pemeriksaan kelelahan.
closed class C;
class D1 : C;
class D2 : C;
class Program
{
int M1<X>(X x) where X : C
=> x switch
{
D1 => 1,
D2 => 2,
};
int M2<X>(X x) where X : C
=> x switch
{
D1 => 1,
D2 => 2,
C => 3, // error: 'C' is subsumed by the previous cases
};
}
Menentukan subjenis kelas tertutup
Kelelahan sakelar atas jenis kelas tertutup, ditentukan dengan memeriksa apakah sakelar habis di atas set subjenis jenis kelas tertutup input.
Sekumpulan subjenis S kelas tertutup ditentukan dengan cara berikut:
- Untuk jenis
Ctertutup tertentu, biarkanC₀definisi aslinya. - Untuk setiap deklarasi
S₀subjenis yang jenis dasarnya memiliki definisiC₀asli , tentukan apakah ada konstruksiSyang memiliki jenisCdasar .- Lihat juga §19.6.3 Keunikan antarmuka yang diimplementasikan dalam standar.
- Jika seperti
Situ ada, itu termasuk dalam set subjenis.
Konversi antarmuka kelas tertutup
Kelas tertutup dikatakan memiliki hierarki yang disegel, jika semua subjenisnya disegel atau memiliki hierarki yang disegel. Artinya, semua kelas dalam hierarki yang diperluas disegel atau ditutup.
Ketika kelas tertutup memiliki hierarki yang disegel, maka pembatasan konvertibilitas antarmuka diperkenalkan. Ini mencegah upaya konversi ke jenis antarmuka, yang tidak mungkin berhasil.
Pembatasan ini bersifat mirip dengan konversi referensi eksplisit dari jenis kelas yang disegel ke jenis antarmuka. Lihat §10.3.5 Konversi referensi eksplisit.
var c = new C();
var i = (I)c; // error
closed class C { }
sealed class D1 : C { }
sealed class D2 : C { }
interface I { }
Kami menentukan apakah konversi referensi eksplisit dari C ke I ada, dengan secara rekursif mengumpulkan set antarmuka yang diterapkan oleh C dan subjenisnya. Jika kumpulan antarmuka mencakup I, dan C tidak menerapkan I, maka konversi referensi eksplisit ada dari C ke I. (Dalam kasus yang C mengimplementasikan , maka konversi referensi implisit Itersedia sebagai gantinya.)
Menurunkan
Kelas tertutup dihasilkan dengan IsClosedType atribut , untuk memungkinkan mereka dikenali oleh pengkompilasi yang mengonsumsi.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class IsClosedTypeAttribute : Attribute { }
}
Memblokir subjenis dari bahasa/kompilator lain
Kelas tertutup tidak boleh diwariskan dari bahasa yang tidak mendukung kelas tertutup. Ini dicapai dengan menambahkan [CompilerFeatureRequired("ClosedClasses")] ke semua konstruktor kelas tertutup.
// Authoring assembly, built with .NET 10 SDK
closed class C1
{
public C1() { }
public C1(int param) { }
}
// Consuming assembly, built with .NET 8 SDK
class C2 : C1
{
public C2() { } // error: 'C1.C1()' requires compiler feature "ClosedClasses"
public C2() : base(42) { } // error: 'C1.C1(int)' requires compiler feature "ClosedClasses"
}
Metadata "tampilan" dari C1:
[IsClosedType]
class C1
{
[CompilerFeatureRequired("ClosedClasses")]
public C1() { }
[CompilerFeatureRequired("ClosedClasses")]
public C1(int param) { }
}
Perhatikan bahwa tidak seperti fitur "anggota yang diperlukan", ObsoleteAttribute tidak dipancarkan selain CompilerFeatureRequiredAttribute. Hanya yang terakhir yang dipancarkan.
Beberapa CompilerFeatureRequiredAttributes
Dalam skenario seperti berikut, pengkompilasi akan memancarkan fitur terpisah CompilerFeatureRequired, untuk setiap fitur yang diperlukan yang relevan dengan simbol:
closed class C1
{
public C() { }
public required string P { get; set; }
}
// Metadata:
class C1
{
[Obsolete("Types with required members are not supported in this version of your compiler")]
[CompilerFeatureRequired("RequiredMembers")]
[CompilerFeatureRequired("ClosedClasses")]
public C1() { }
}
Kekurangan
- Ini bisa menjadi perubahan yang melanggar untuk menambahkan
closedpengubah ke kelas yang ada, atau untuk menambahkan kelas turunan tambahan dari kelas tertutup. Sebelum menerbitkan kelas tertutup, penulis perlu mempertimbangkan kontrak jangka panjang yang tersirat dengan konsumennya.
Alternatives
- Alih-alih pengubah baru
closed, kelas tertutup dapat ditunjuk dengan[Closed]atribut . - Cakupan di mana keturunan diizinkan dapat dipersempit lebih jauh ke file (meskipun itu tidak akan memiliki banyak preseden di C#) atau ke dalam tubuh kelas tertutup sebagai kelas berlapis.
- Kumpulan tertutup turunan yang diizinkan dapat diberikan sebagai daftar alih-alih tersirat oleh di mana deklarasi terjadi. Ini akan memungkinkan penyertaan kelas di rakitan lain.
Fitur opsional
- Antarmuka juga dapat diizinkan untuk ditutup. Aturannya akan sangat mirip.
Buka pertanyaan
Tidak tersedia
C# feature specifications