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.
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 tertuangkan 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 .
Edisi unggulan: https://github.com/dotnet/csharplang/issues/9662
RINGKASAN
Unions adalah sekumpulan fitur interlink, yang digabungkan untuk memberikan dukungan C# untuk jenis serikat:
-
Jenis gabungan: Struktur dan kelas yang memiliki
[Union]atribut dikenali sebagai jenis serikat, dan mendukung perilaku serikat. - Jenis kasus: Jenis union memiliki sekumpulan jenis kasus, yang diberikan oleh parameter untuk konstruktor dan metode pabrik.
-
Perilaku serikat: Jenis serikat mendukung perilaku serikat berikut:
- Konversi serikat pekerja: Ada konversi serikat implisit dari setiap jenis kasus ke jenis serikat pekerja.
- Pencocokan gabungan: Pencocokan pola terhadap nilai gabungan secara implisit "membongkar" kontennya, menerapkan pola ke nilai yang mendasar sebagai gantinya.
- Kelelahan union: Beralih ekspresi atas nilai union habis ketika semua jenis kasus telah dicocokkan, tanpa perlu kasus fallback.
- Union nullability: Analisis nullability telah meningkatkan pelacakan status null konten serikat.
- Pola gabungan: Semua jenis serikat mengikuti pola serikat dasar, tetapi ada pola opsional tambahan untuk skenario tertentu.
- Deklarasi serikat: Sintaks singkat memungkinkan deklarasi jenis serikat secara langsung. Implementasinya "dipengaruhi" - deklarasi struktur yang mengikuti pola serikat dasar dan menyimpan konten sebagai bidang referensi tunggal.
- Antarmuka serikat: Beberapa antarmuka dikenal oleh bahasa dan digunakan dalam implementasi deklarasi serikat.
Motivasi
Serikat buruh adalah fitur C# yang diminta lama, yang memungkinkan mengekspresikan nilai dari sekumpulan jenis tertutup dengan cara yang dapat dipercaya pencocokan pola menjadi lengkap.
Pemisahan antara jenis serikat dan deklarasi serikat memungkinkan C# untuk memiliki sintaks deklarasi serikat yang tepat dengan semantik berpendapat, sekaligus memungkinkan jenis atau jenis yang ada dengan pilihan implementasi lain untuk memilih perilaku serikat.
Serikat yang diusulkan dalam C# adalah serikat jenis dan bukan "diskriminasi" atau "ditandai". "Serikat yang didiskriminasi" dapat dinyatakan dalam hal "jenis serikat" dengan menggunakan deklarasi jenis baru sebagai jenis kasus. Atau mereka dapat diimplementasikan sebagai hierarki tertutup, yang merupakan fitur C# lain yang terkait dan akan datang yang berfokus pada kelelahan.
Desain terperinci
Jenis serikat pekerja
Kelas atau jenis struktur apa pun dengan System.Runtime.CompilerServices.UnionAttribute atribut dianggap sebagai jenis serikat:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(Class | Struct, AllowMultiple = false)]
public class UnionAttribute : Attribute;
}
Jenis serikat harus mengikuti pola tertentu dari anggota serikat publik, yang harus dideklarasikan pada jenis serikat itu sendiri atau didelegasikan ke "penyedia anggota gabungan".
Beberapa anggota serikat wajib, dan yang lain bersifat opsional.
Jenis serikat memiliki sekumpulan jenis kasus yang ditetapkan berdasarkan tanda tangan anggota serikat tertentu.
Konten nilai serikat dapat diakses melalui Value properti. Bahasa mengasumsikan bahwa Value hanya pernah berisi nilai salah satu jenis kasus, atau null (lihat Kesadaran yang baik).
Penyedia anggota gabungan
Secara default, anggota serikat ditemukan pada jenis serikat itu sendiri. Namun, jika jenis serikat secara langsung berisi deklarasi antarmuka yang disebut IUnionMembers maka antarmuka bertindak sebagai penyedia anggota serikat. Dalam hal ini, anggota serikat hanya ditemukan di penyedia anggota serikat, bukan pada jenis serikat itu sendiri.
Antarmuka penyedia anggota serikat harus publik, dan jenis serikat itu sendiri harus mengimplementasikannya sebagai antarmuka.
Kami menggunakan istilah jenis union-defining untuk jenis di mana anggota serikat ditemukan: Penyedia anggota serikat jika ada, dan jenis serikat itu sendiri sebaliknya.
Anggota serikat
Anggota gabungan dicari berdasarkan nama dan tanda tangan pada jenis union-defining. Mereka tidak harus dideklarasikan langsung pada jenis union-defining, tetapi dapat diwariskan.
Ini adalah kesalahan bagi setiap anggota serikat tidak menjadi publik.
Anggota pembuatan dan Value properti wajib, dan secara kolektif disebut sebagai pola serikat dasar.
Anggota HasValue dan TryGetValue secara kolektif disebut sebagai pola akses serikat non-tinju.
Anggota gabungan yang berbeda dijelaskan dalam hal berikut.
Anggota pembuatan serikat
Anggota pembuatan union digunakan untuk membuat nilai union baru dari nilai jenis kasus.
Jika jenis union-defining adalah jenis serikat itu sendiri, setiap konstruktor dengan parameter tunggal adalah konstruktor gabungan. Jenis kasus serikat diidentifikasi sebagai kumpulan jenis yang dibangun dari jenis parameter konstruktor ini dengan cara berikut:
- Jika jenis parameter adalah jenis nullable (baik nilai atau referensi), jenis kasus adalah jenis yang mendasar
- Jika tidak, jenis kasus adalah jenis parameter.
// Union constructor making `Dog` a case type
public Pet(Dog value) { ... }
// Union constructor making `int` a case type
public Union(int? value) { ... }
// Union constructor making `string` a case type
public Union(string? value) { ... }
Jika jenis union-defining adalah penyedia anggota gabungan, setiap metode statis Create dengan parameter tunggal dan jenis pengembalian yang dapat dikonversi identitas ke jenis serikat pekerja itu sendiri adalah metode pabrik gabungan.
Jenis kasus serikat diidentifikasi sebagai kumpulan jenis yang dibangun dari jenis parameter metode pabrik ini dengan cara berikut:
- Jika jenis parameter adalah jenis nullable (baik nilai atau referensi), jenis kasus adalah jenis yang mendasar
- Jika tidak, jenis kasus adalah jenis parameter.
// Union factory method making `Cat` a case type
public static Pet Create(Cat value) { ... }
// Union factory method making `int` a case type
public static Union Create(int? value) { ... }
// Union factory method making `string` a case type
public static Union Create(string? value) { ... }
Konstruktor serikat pekerja dan metode pabrik serikat disebut secara kolektif sebagai anggota pembuatan serikat pekerja.
Parameter tunggal anggota pembuatan serikat harus berupa nilai demi nilai atau in parameter.
Jenis serikat harus memiliki setidaknya satu anggota pembuatan serikat, dan oleh karena itu setidaknya satu jenis kasus.
Properti nilai
Properti Value memungkinkan akses ke nilai yang terkandung dalam serikat, terlepas dari jenis kasusnya.
Setiap jenis union-defining harus mendeklarasikan Value properti jenis object? atau object. Properti harus memiliki get aksesor dan mungkin secara opsional memiliki init aksesor atau set , yang dapat memiliki aksesibilitas apa pun dan tidak digunakan oleh pengkompilasi.
// Union 'Value' property
public object? Value { get; }
Anggota akses non-tinju
Jenis serikat dapat memilih untuk mengimplementasikan pola akses gabungan non-tinju, yang memungkinkan akses kondisional yang sangat ditik ke setiap jenis kasus, serta cara untuk memeriksa null.
Ini memungkinkan pengkompilasi untuk menerapkan pencocokan pola secara lebih efisien ketika jenis kasus adalah jenis nilai dan disimpan seperti dalam serikat.
Anggota akses non-tinju adalah:
- Properti
HasValuejenisbooldengan aksesor publikget. Ini mungkin secara opsional memiliki atauinitsetaksesor, yang dapat memiliki aksesibilitas apa pun dan tidak digunakan oleh pengkompilasi. - Metode
TryGetValueuntuk setiap jenis kasus. Metode mengembalikanbooldan mengambil parameter keluar tunggal dari jenis yang dapat dikonversi identitas ke jenis huruf besar/kecil.
// Non-boxing access members
public bool HasValue { get { ... } }
public bool TryGetValue(out Dog value) { ... }
HasValue diharapkan untuk mengembalikan true jika dan hanya jika serikat Value tidak null.
TryGetValue diharapkan untuk mengembalikan true jika dan hanya jika serikat Value adalah dari jenis kasus yang diberikan, dan jika demikian, berikan nilai tersebut dalam parameter keluar metode.
Kesadaran yang baik
Bahasa dan kompilator membuat sejumlah asumsi perilaku tentang jenis serikat. Jika jenis memenuhi syarat sebagai jenis serikat tetapi tidak memenuhi asumsi tersebut, maka perilaku serikat pekerja mungkin tidak berfungsi seperti yang diharapkan.
-
Kedengaran
Value: Properti selalu mengevaluasi ke null atau ke nilai jenis kasus. Itu benar bahkan untuk nilai default jenis union. -
Stabilitas: Jika nilai union dibuat dari jenis kasus,
Valueproperti akan cocok dengan jenis kasus tersebut atau null. Jika nilai union dibuat darinullnilai,Valueproperti akan menjadinull. - Kesetaraan pembuatan: Jika nilai secara implisit dapat dikonversi ke dua jenis kasus yang berbeda, maka anggota pembuatan untuk salah satu jenis kasus tersebut memiliki perilaku yang dapat diamati yang sama ketika dipanggil dengan nilai tersebut.
-
Konsistensi pola akses: Perilaku
HasValueanggota akses non-tinju danTryGetValue, jika ada, dapat diamati setara dengan pemeriksaan terhadapValueproperti secara langsung.
Contoh jenis serikat
Pet mengimplementasikan pola serikat dasar pada jenis serikat itu sendiri:
[Union] public record struct Pet
{
// Creation members = case types are 'Dog' and 'Cat'
public Pet(Dog value) => Value = value;
public Pet(Cat value) => Value = value;
// 'Value' property
public object? Value { get; }
}
IntOrBool menerapkan pola akses non-tinju pada jenis serikat itu sendiri:
public record struct IntOrBool
{
private bool _isBool;
private int _value;
public IntOrBool(int value) => (_isBool, _value) = (false, value);
public IntOrBool(bool value) => (_isBool, _value) = (true, value ? 1 : 0);
public object Value => _isBool ? _value is 1 : _value;
public bool HasValue => true;
public bool TryGetValue(out int value)
{
value = _value;
return !_isBool;
}
public bool TryGetValue(out bool value)
{
value = _isBool && _value is 1;
return _isBool;
}
}
Catatan: Ini hanyalah contoh bagaimana pola akses non-tinju mungkin diterapkan. Kode pengguna dapat menyimpan konten dengan cara apa pun yang disukainya. Secara khusus, itu tidak mencegah implementasi dari tinju! Dalam non-boxing namanya mengacu pada memungkinkan implementasi pencocokan pola kompilator untuk mengakses setiap jenis kasus dengan cara yang sangat ditik, dibandingkan object?dengan properti yang ditik Value .
Result<T> menerapkan pola dasar melalui penyedia anggota serikat:
public record class Result<T> : Result<T>.IUnionMembers
{
object? _value;
public interface IUnionMembers
{
public static Result<T> Create(T value) => new() { _value = value };
public static Result<T> Create(Exception value) => new() { _value = value };
public object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
Perilaku serikat
Perilaku serikat umumnya diimplementasikan dengan cara pola serikat dasar. Jika serikat menawarkan pola akses non-tinju, pencocokan pola serikat akan lebih disukai untuk menggunakannya.
Konversi serikat pekerja
Konversi serikat pekerja secara implisit dikonversi ke jenis serikat dari setiap jenis kasusnya. Secara khusus, ada konversi serikat ke jenis U serikat pekerja dari jenis atau ekspresi E jika ada konversi implisit standar dari E ke jenis C dan C merupakan jenis parameter anggota pembuatan serikat dari U.
Jika jenis U union adalah struct, ada konversi serikat pekerja untuk mengetik U? dari jenis atau ekspresi E jika ada konversi implisit standar dari E ke jenis C dan C merupakan jenis parameter dari anggota pembuatan serikat dari U.
Konversi serikat bukan konversi implisit standar. Oleh karena itu mungkin tidak berpartisipasi dalam konversi implisit yang ditentukan pengguna atau konversi serikat lainnya.
Tidak ada konversi serikat eksplisit di luar konversi serikat implisit. Dengan demikian, bahkan jika ada konversi eksplisit dari E ke jenis Ckasus serikat pekerja , itu tidak berarti ada konversi eksplisit dari E ke jenis serikat pekerja tersebut.
Konversi gabungan dijalankan dengan memanggil anggota pembuatan serikat pekerja:
Pet pet = dog;
// becomes
Pet pet = new Pet(dog);
// and
Result<string> result = "Hello"
//becomes
Result<string> result = Result<string>.IUnionMembers.Create("Hello");
Ini adalah kesalahan jika resolusi kelebihan beban tidak menemukan satu anggota kandidat terbaik, atau jika anggota tersebut bukan salah satu anggota gabungan jenis serikat.
Konversi gabungan hanyalah "bentuk" lain dari konversi implisit yang ditentukan pengguna. Konversi serikat "bayangan" operator konversi yang ditentukan pengguna yang berlaku.
Alasan di balik keputusan ini:
Jika seseorang menulis operator yang ditentukan pengguna, operator tersebut akan mendapatkan prioritas. Dengan kata lain, jika pengguna benar-benar menulis operator mereka sendiri, mereka ingin kita memanggilnya. Jenis yang ada dengan operator konversi yang diubah menjadi jenis serikat pekerja terus bekerja dengan cara yang sama sehubungan dengan kode yang ada yang menggunakan operator saat ini.
Dalam contoh berikut, konversi implisit yang ditentukan pengguna lebih diprioritaskan daripada konversi gabungan.
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
}
Dalam contoh berikut, ketika pemeran eksplisit digunakan dalam kode, konversi eksplisit yang ditentukan pengguna lebih diprioritaskan daripada konversi serikat pekerja. Tetapi, ketika tidak ada cast eksplisit dalam kode, konversi serikat digunakan karena konversi eksplisit yang ditentukan pengguna tidak berlaku.
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
Pencocokan union
Ketika nilai masuk dari pola adalah jenis gabungan atau dari jenis gabungan yang dapat diubah ke null, nilai nullable dan konten nilai union yang mendasar mungkin "dibongkar", tergantung pada polanya.
Untuk pola dan var tanpa syarat_, pola diterapkan ke nilai masuk itu sendiri. Contohnya:
if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet`
Namun, semua pola lainnya diterapkan secara implisit ke properti serikat yang Value mendasar:
if (GetPet() is Dog dog) { ... } // 'Dog dog' is applied to 'GetPet().Value'
if (GetPet() is null) { ... } // 'null' is applied to 'GetPet().Value'
if (GetPet() is { } value) { ... } // '{ } value' is applied to 'GetPet().Value'
Untuk pola logis, aturan ini diterapkan satu per satu ke cabang, mengingat bahwa cabang and kiri pola dapat memengaruhi jenis masuk cabang kanan:
GetPet() switch
{
var pet and not null => ... // 'var pet' applies to the incoming 'Pet' and 'not null' to its 'Value'
not null and var value => ... // 'not null' applies to the 'Value' as does 'var value' because of the
// left branch changing the incoming type to `object?`.
}
Catatan: Aturan ini berarti bahwa GetPet() is Pet pet kemungkinan tidak akan berhasil, seperti Pet yang diterapkan pada konten, bukan ke serikat itu Pet sendiri.
Catatan: Alasan untuk perlakuan yang berbeda dari pola tanpa syarat var (serta _, yang pada dasarnya merupakan singkatan dari var _) adalah asumsi bahwa penggunaannya secara kualitatif berbeda dari pola lain.
var pola digunakan hanya untuk memberi nama nilai yang dicocokkan dengan, sering kali dalam pola berlapis, seperti PetOwner{ Pet: var pet }. Di sini, semantik yang membantu adalah untuk pet mempertahankan jenis Petserikat , alih-alih Value properti didereferensikan ke jenis yang tidak berguna object? .
Jika nilai masuk adalah jenis kelas, maka null pola akan berhasil terlepas dari apakah nilai gabungan itu sendiri atau null nilai yang terkandung adalah null:
if (result is null) { ... } // if (result == null || result.Value == null)
Pola pencocokan serikat lainnya hanya akan berhasil ketika nilai gabungan itu sendiri bukan null.
if (result is 1) { ... } // if (result != null && result.Value is 1)
Demikian pula, jika nilai masuk adalah jenis nilai nullable (membungkus jenis union struct), maka null pola akan berhasil terlepas dari apakah nilai masuk itu null sendiri atau nilai yang terkandung adalah null:
if (result is null) { ... } // if (result.HasValue == false || result.GetValueOrDefault().Value == null)
Pola pencocokan gabungan lainnya hanya akan berhasil ketika nilai masuk itu sendiri bukan null.
if (result is 1) { ... } // if (result.HasValue && result.GetValueOrDefault().Value is 1)
Pengkompilasi akan lebih suka menerapkan perilaku pola dengan cara anggota yang ditentukan oleh pola akses non-tinju. Meskipun bebas untuk melakukan pengoptimalan apa pun dalam batas aturan kemapanan, berikut ini adalah set minimum yang dijamin akan diterapkan:
- Untuk pola yang menyiratkan pemeriksaan jenis
Ttertentu , jikaTryGetValue(S value)metode tersedia, dan ada identitas, atau konversi referensi/tinju implisit dariTkeS, maka metode tersebut digunakan untuk mendapatkan nilai. Pola kemudian diterapkan ke nilai tersebut. Jika ada lebih dari satu metode tersebut, maka dari mana pun konversi dariTkeSbukan konversi tinju lebih disukai jika tersedia. Jika masih ada lebih dari satu metode, metode dipilih dengan cara yang ditentukan implementasi. - Jika tidak, untuk pola yang menyiratkan pemeriksaan ,
nulljikaHasValueproperti tersedia, properti tersebut digunakan untuk memeriksa apakah nilai gabungan null. - Jika tidak, pola diterapkan ke hasil mengakses
IUnion.Valueproperti pada serikat masuk.
Operator is-type yang diterapkan ke jenis gabungan memiliki arti yang sama dengan pola jenis yang diterapkan ke jenis gabungan.
Kelelahan serikat
Jenis serikat diasumsikan "kelelahan" oleh jenis kasusnya. Ini berarti bahwa switch ekspresi lengkap jika menangani semua jenis kasus serikat:
var name = pet switch
{
Dog dog => ...,
Cat cat => ...,
// No warning about non-exhaustive switch
};
Keterbatalan
Status null properti serikat dilacak Value seperti properti lainnya, dengan modifikasi ini:
- Ketika anggota pembuatan serikat dipanggil (secara eksplisit atau melalui konversi serikat pekerja), serikat pekerja baru
Valuemendapatkan status null dari nilai masuk. - Ketika pola
HasValueakses non-tinju atauTryGetValue(...)digunakan untuk mengkueri konten jenis gabungan (secara eksplisit atau melalui pencocokan pola), itu berdampak padaValuestatus nullability dengan cara yang sama seperti jikaValuetelah dicentang secara langsung: Status nullValuemenjadi "tidak null" padatruecabang.
Bahkan ketika sakelar gabungan dinyatakan lengkap, jika status null properti serikat Value masuk adalah "mungkin null", peringatan akan diberikan pada null yang tidak tertangani.
Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null"
var value = pet switch
{
Dog dog => ...,
Cat cat => ...,
// Warning: 'null' not handled
}
Antarmuka union
Antarmuka berikut digunakan oleh bahasa dalam implementasi fitur serikat.
Antarmuka akses union
Antarmuka IUnion menandai jenis sebagai jenis gabungan pada waktu kompilasi dan menyediakan cara untuk mengakses konten gabungan saat runtime.
public interface IUnion
{
// The value of the union or null
object? Value { get; }
}
Serikat yang dihasilkan oleh pengkompilasi mengimplementasikan antarmuka ini.
Contoh penggunaan:
if (value is IUnion { Value: null }) { ... }
Deklarasi serikat
Deklarasi serikat adalah cara singkat dan berpendapat untuk mendeklarasikan jenis serikat di C#. Mereka mendeklarasikan struct yang menggunakan referensi objek tunggal untuk menyimpannya Value, yang berarti:
- Tinju: Jenis nilai apa pun di antara jenis kasusnya akan dikotak pada entri.
- Kekompakan: Nilai union hanya berisi satu bidang.
Niatnya adalah untuk deklarasi serikat untuk mencakup sebagian besar kasus penggunaan dengan cukup baik. Dua alasan utama untuk pengkodean tangan jenis serikat tertentu daripada menggunakan deklarasi serikat diharapkan adalah:
- Mengadaptasi jenis yang ada dengan pola serikat untuk mendapatkan perilaku serikat.
- Menerapkan strategi penyimpanan yang berbeda untuk misalnya efisiensi atau alasan interop.
Sintaksis
Deklarasi serikat memiliki nama dan daftar jenis konstruktor serikat pekerja.
union_declaration
: attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list?
'(' type (',' type)* ')' struct_interfaces? type_parameter_constraints_clause*
(`{` struct_member_declaration* `}` | ';')
;
Selain pembatasan anggota struct (ยง16.3), hal berikut berlaku untuk anggota serikat:
- Bidang instans, properti otomatis, atau peristiwa seperti bidang tidak diizinkan.
- Konstruktor publik yang dideklarasikan secara eksplisit dengan satu parameter tidak diizinkan.
- Konstruktor yang dinyatakan secara eksplisit harus menggunakan
this(...)penginisialisasi untuk (secara langsung atau tidak langsung) mendelegasikan ke salah satu konstruktor yang dihasilkan.
Jenis konstruktor serikat pekerja dapat berupa jenis apa pun yang dikonversi ke object, misalnya, antarmuka, parameter jenis, jenis nullable, dan serikat pekerja lainnya. Tidak masalah untuk kasus yang dihasilkan tumpang tindih, dan bagi serikat untuk bersarang atau null.
Examples:
// Union of existing types
public union Pet(Cat, Dog, Bird);
// Union with function member
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
IEnumerable<T> list => list,
T value => [value],
}
}
// "Discriminated" union with freshly declared case types
public record class None();
public record class Some<T>(T value);
public union Option<T>(None, Some<T>);
#### Lowering
A union declaration is lowered to a struct declaration with
* the same attributes, modifiers, name, type parameters and constraints,
* implicit implementations of `IUnion`,
* a `public object? Value { get; }` auto-property,
* a public constructor for each *union constructor* type,
* any members in the union declaration's body.
It is an error for user-declared members to conflict with generated members.
Example:
``` c#
public union Pet(Cat, Dog){ ... }
Diturunkan ke:
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public object? Value { get; }
... // original body
}
Buka pertanyaan
[Diselesaikan] Apakah deklarasi serikat menjadi catatan?
Deklarasi serikat diturunkan ke struktur rekaman
Saya pikir perilaku default ini tidak perlu dan, mengingat bahwa perilaku tersebut tidak dapat dikonfigurasi, akan secara signifikan membatasi skenario penggunaan. Rekaman menghasilkan banyak kode yang tidak digunakan atau tidak cocok dengan persyaratan tertentu. Misalnya, rekaman cukup banyak dilarang dalam basis kode kompilator karena kode yang membentang. Saya berpikir bahwa akan lebih baik untuk mengubah default:
- Secara default, deklarasi serikat menyatakan struktur reguler hanya dengan anggota khusus serikat.
- Pengguna dapat mendeklarasikan penyatuan rekaman:
record union U(E1, ...) ...
Resolusi: Deklarasi serikat adalah struktur biasa, bukan struktur rekaman.
record union ... tidak didukung
[Diselesaikan] Sintaksis deklarasi serikat
Sepertinya sintaks yang diusulkan tidak lengkap atau tidak perlu dibatasi. Misalnya, sepertinya klausul dasar tidak diizinkan. Namun, saya dapat dengan mudah membayangkan kebutuhan untuk mengimplementasikan antarmuka, misalnya.
Saya pikir selain dari elemen-type-list sintaksis harus cocok dengan deklarasi reguler structrecord struct/di mana struct kata kunci diganti dengan union kata kunci.
Resolusi: Pembatasan dihapus.
[Diselesaikan] Anggota deklarasi serikat
Bidang instans, properti otomatis, atau peristiwa seperti bidang tidak diizinkan.
Ini terasa segan-segan dan benar-benar tidak perlu.
Resolusi: Pembatasan disimpan.
[Diselesaikan] Jenis nilai nullable sebagai jenis kasus Union
Jenis kasus serikat diidentifikasi sebagai kumpulan jenis parameter dari konstruktor ini. Jenis kasus serikat diidentifikasi sebagai kumpulan jenis parameter dari metode pabrik ini.
Pada saat yang sama:
Metode
TryGetValueuntuk setiap jenis kasus. Metode mengembalikanbooldan mengambil parameter keluar tunggal dari jenis yang sesuai dengan jenis kasus yang diberikan dengan cara berikut:
- Jika jenis kasus adalah jenis nilai nullable, jenis parameter harus dapat dikonversi identitas ke jenis yang mendasar
- Jika tidak, jenisnya harus dapat dikonversi identitas ke jenis kasus.
Apakah ada keuntungan untuk memiliki jenis nilai nullable di antara jenis kasus terutama bahwa pola jenis tidak dapat menggunakan jenis nilai nullable sebagai jenis target? Rasanya seperti kita hanya bisa mengatakan bahwa, jika jenis parameter konstruktor/pabrik adalah jenis nilai nullable, maka jenis kasus yang sesuai adalah jenis yang mendasar. Kemudian kita tidak akan membutuhkan klausul tambahan untuk metode ini TryGetValue , semua parameter keluar adalah jenis kasus.
Resolusi: Saran disetujui
[Diselesaikan] Status properti default yang dapat diubah ke Value null
Untuk jenis union di mana tidak ada jenis kasus yang dapat diubah ke null, status default untuk
Valueadalah "not null" daripada "mungkin null".
Dengan desain baru, di mana Value properti tidak didefinisikan dalam beberapa antarmuka umum, tetapi merupakan API yang secara khusus termasuk dalam jenis yang dideklarasikan, aturan yang dikutip di atas terasa seperti over-engineering. Selain itu, aturan kemungkinan akan memaksa konsumen untuk menggunakan jenis nullable dalam situasi di mana jenis nullable tidak akan digunakan.
Misalnya, pertimbangkan deklarasi serikat berikut:
union U1(int, bool, DateTime);
Menurut aturan yang dikutip, status default untuk Value adalah "bukan null". Tetapi itu tidak cocok dengan perilaku jenisnya, default(U1).Value adalah null. Untuk merealign perilaku, konsumen dipaksa untuk membuat setidaknya satu jenis kasus nullable. Sesuatu seperti:
union U1(int?, bool, DateTime);
Tetapi itu kemungkinan tidak diinginkan, konsumen mungkin tidak ingin mengizinkan pembuatan eksplisit dengan int? nilai.
Proposal: Hapus aturan yang dikutip, analisis nullable harus menggunakan anotasi dari Value properti untuk menyimpulkan nullability defaultnya.
Resolusi: Proposal disetujui
[Diselesaikan] Union yang cocok untuk Nullable dari tipe nilai union
Ketika nilai masuk dari pola adalah jenis gabungan, konten nilai gabungan mungkin "dibongkar", tergantung pada polanya.
Haruskah kita memperluas aturan ini ke skenario ketika nilai masuk dari pola adalah dari Nullable<union type>?
Pertimbangkan skenario berikut:
static bool Test1(StructUnion? u)
{
return u is 1;
}
static bool Test2(ClassUnion? u)
{
return u is 1;
}
Arti u is 1 dalam Test1 dan Test2 sangat berbeda. Di Test1 itu bukan pencocokan serikat, di Test2 itu.
Mungkin "pencocokan serikat" harus "menggali" melalui Nullable<T> pencocokan pola yang biasanya dilakukan dalam situasi lain.
Jika kita pergi dengan itu, maka pola pencocokan null serikat pekerja terhadap Nullable<union type> harus bekerja sebagai melawan kelas.
Yaitu polanya benar ketika (!nullableValue.HasValue || nullableValue.Value.Value is null).
Resolusi: Proposal disetujui.
Apa yang harus dilakukan tentang API "buruk"?
Apa yang harus dilakukan kompilator tentang UNION matching API yang terlihat seperti kecocokan, tetapi sebaliknya "buruk"? Misalnya, compiler menemukan TryGetValue/HasValue dengan tanda tangan yang cocok, tetapi "buruk" karena pengubah kustom yang diperlukan atau memerlukan fitur yang tidak diketahui, dll. Haruskah pengkompilasi secara diam-diam mengabaikan API atau melaporkan kesalahan? Serupa, API mungkin ditandai sebagai Usang/Eksperimental. Haruskah pengkompilasi melaporkan diagnostik apa pun, secara diam-diam menggunakan API atau secara diam-diam tidak menggunakan API?
Bagaimana jika jenis untuk deklarasi serikat tidak ada
Apa yang terjadi jika UnionAttribute, IUnion atau IUnion<TUnion> hilang? Kesalahan? Mensintesis? Sesuatu yang lain?
[Diselesaikan] Desain antarmuka IUnion generik
Argumen telah dibuat yang IUnion<TUnion> seharusnya tidak mewarisi dari IUnion atau membatasi parameter jenisnya ke IUnion<TUnion>. Kita harus mengunjungi kembali.
Resolusi: Antarmuka IUnion<TUnion> dihapus untuk saat ini.
[Diselesaikan] Jenis nilai nullable sebagai jenis kasus dan interaksinya dengan TryGetValue
Aturan di atas menyatakan bahwa jika jenis kasus adalah jenis nilai yang dapat diubah ke null, jenis parameter yang digunakan dalam metode yang TryGetValue sesuai harus menjadi jenis yang mendasar .
Ini dimotivasi oleh fakta bahwa nilai null tidak akan pernah dihasilkan melalui metode ini. Di sisi konsumsi, jenis nilai nullable tidak diizinkan sebagai pola jenis, sedangkan kecocokan terhadap jenis yang mendasar harus dapat memetakan ke panggilan metode ini.
Kita harus mengkonfirmasi bahwa kita setuju dengan pembungkusan ini.
Resolusi: Disepakati/dikonfirmasi
Pola akses gabungan non-tinju
Perlu menentukan aturan yang tepat untuk menemukan API dan TryGetValue yang sesuaiHasValue.
Apakah warisan terlibat? Apakah baca/tulis HasValue kecocokan yang dapat diterima? Dll.
[Diselesaikan] TryGetValue konversi yang cocok
Bagian Union Matching mengatakan:
Untuk pola yang menyiratkan pemeriksaan jenis
Ttertentu , jikaTryGetValue(S value)metode tersedia, dan ada konversi implisit dariTkeS, maka metode tersebut digunakan untuk mendapatkan nilai.
Apakah kumpulan konversi implisit dibatasi dengan cara apa pun? Misalnya, apakah konversi yang ditentukan pengguna diizinkan? Bagaimana dengan konversi tuple dan konversi lain yang tidak begitu sepele? Beberapa di antaranya bahkan konversi standar.
Apakah set TryGetValue metode dibatasi dengan cara lain? Misalnya, bagian Pola Union menyiratkan bahwa hanya metode dengan jenis parameter yang cocok dengan jenis kasus yang dipertimbangkan:
public bool TryGetValue(out T value)metode untuk setiap jenisTkasus .
Akan lebih baik untuk memiliki jawaban eksplisit.
Resolusi: Hanya identitas implisit, atau referensi, atau konversi tinju yang dipertimbangkan
TryGetValue dan analisis nullable
Ketika pola
HasValueakses non-tinju atauTryGetValue(...)digunakan untuk mengkueri konten jenis gabungan (secara eksplisit atau melalui pencocokan pola), itu berdampak padaValuestatus nullability dengan cara yang sama seperti jikaValuetelah dicentang secara langsung: Status nullValuemenjadi "tidak null" padatruecabang.
Apakah set TryGetValue metode dibatasi dengan cara apa pun? Misalnya, bagian Pola Union menyiratkan bahwa hanya metode dengan jenis parameter yang cocok dengan jenis kasus yang dipertimbangkan:
public bool TryGetValue(out T value)metode untuk setiap jenisTkasus .
Akan lebih baik untuk memiliki jawaban eksplisit.
Mengklarifikasi aturan sekeliling default nilai jenis struct union
Catatan: Aturan nullability default yang disebutkan di bawah ini telah dihapus.
Catatan: Aturan kesempurnaan "default" yang disebutkan di bawah ini telah dihapus. Kita harus mengkonfirmasi bahwa ini adalah apa yang kita inginkan.
Bagian nullability mengatakan:
Untuk jenis union di mana tidak ada jenis kasus yang dapat diubah ke null, status default untuk
Valueadalah "not null" daripada "mungkin null".
Mengingat bahwa, untuk contoh di bawah ini, implementasi saat ini mempertimbangkan Values2 sebagai "bukan null":
S2 s2 = default;
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => throw null!;
public S2(bool x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
Pada saat yang sama, bagian Well-formedness mengatakan:
- Nilai default: Jika jenis union adalah jenis nilai, nilai defaultnya adalah nilai default sebagai
nullValue.- Konstruktor default: Jika jenis serikat memiliki konstruktor nullary (tanpa argumen), union yang dihasilkan memiliki
nullsebagaiValue.
Implementasi seperti itu akan bertentangan dengan perilaku analisis nullable untuk contoh di atas.
Haruskah aturan Well-formedness disesuaikan, atau harus status Valuedefault menjadi "mungkin null"?
Jika yang terakhir, haruskah inisialisasi S2 s2 = default; menghasilkan peringatan nullability?
Konfirmasikan bahwa parameter jenis tidak pernah merupakan jenis serikat, bahkan ketika dibatasi untuk satu.
class C1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}
class Program
{
static bool Test1<T>(T u) where T : C1
{
return u is int; // Not a union matching
}
static bool Test2<T>(T u) where T : C1
{
return u is string; // Not a union matching
}
}
Haruskah atribut pasca-kondisi memengaruhi nullability default instans Union?
Catatan: Aturan nullability default yang disebutkan di bawah ini telah dihapus. Dan kami tidak lagi menyimpulkan nullability Value default properti dari metode pembuatan serikat. Oleh karena itu, pertanyaannya usang/tidak lagi berlaku untuk desain saat ini.
Untuk jenis union di mana tidak ada jenis kasus yang dapat diubah ke null, status default untuk
Valueadalah "not null" daripada "mungkin null".
Apakah peringatan diharapkan dalam skenario berikut
#nullable enable
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null!;
public S1([System.Diagnostics.CodeAnalysis.NotNull] bool? x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
class Program
{
static void Test2(S1 s)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
_ = s switch { int => 1, bool => 3 }; //
}
}
Konversi serikat pekerja
[Diselesaikan] Di mana mereka berada di antara konversi lain yang diprioritaskan?
Konversi gabungan terasa seperti bentuk lain dari konversi yang ditentukan pengguna. Oleh karena itu, implementasi saat ini mengklasifikasikannya tepat setelah upaya yang gagal untuk mengklasifikasikan konversi implisit yang ditentukan pengguna, dan, jika keberadaan diperlakukan hanya sebagai bentuk lain dari konversi yang ditentukan pengguna. Ini memiliki konsekuensi berikut:
- Konversi implisit yang ditentukan pengguna lebih diprioritaskan daripada konversi serikat pekerja
- Saat pemeran eksplisit digunakan dalam kode, konversi eksplisit yang ditentukan pengguna lebih diprioritaskan daripada konversi serikat pekerja
- Ketika tidak ada pemeran eksplisit dalam kode, konversi serikat mengambil prioritas atas konversi eksplisit yang ditentukan pengguna
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
Perlu mengonfirmasi ini adalah perilaku yang kita sukai. Jika tidak, aturan konversi harus diklarifikasi.
Resolusi:
Disetujui oleh grup kerja.
[Diselesaikan] Ref-ness parameter konstruktor
Bahasa saat ini hanya memungkinkan menurut nilai dan in parameter untuk operator konversi yang ditentukan pengguna.
Rasanya alasan pembatasan ini juga berlaku untuk konstruktor yang cocok untuk konversi serikat pekerja.
Proposal:
Sesuaikan definisi case type constructor di bagian di Union types atas:
-For each public constructor with exactly one parameter, the type of that parameter is considered a *case type* of the union type.
+For each public constructor with exactly one **by-value or `in`** parameter, the type of that parameter is considered a *case type* of the union type.
Resolusi:
Disetujui oleh grup kerja untuk saat ini. Namun, kita mungkin mempertimbangkan "memisahkan" kumpulan konstruktor jenis kasus dan set konstruktor yang cocok untuk konversi jenis serikat pekerja.
[Diselesaikan] Konversi Nullable
Bagian Konversi Nullable secara eksplisit mencantumkan konversi yang dapat digunakan sebagai yang mendasar. Spesifikasi saat ini tidak mengusulkan penyesuaian apa pun pada daftar tersebut. Ini mengakibatkan kesalahan untuk skenario berikut:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1? Test1(int x)
{
return x; // error CS0029: Cannot implicitly convert type 'int' to 'S1?'
}
}
Proposal:
Sesuaikan spesifikasi untuk mendukung konversi implisit nullable dari S ke T? didukung oleh konversi serikat pekerja.
Secara khusus, dengan T asumsi adalah jenis serikat pekerja ada konversi implisit ke jenis T? dari jenis atau ekspresi E jika ada konversi serikat pekerja dari E ke jenis C dan C merupakan jenis Tkasus .
Perhatikan, tidak ada persyaratan untuk jenis E menjadi jenis nilai yang tidak dapat diubah ke null.
Konversi dievaluasi sebagai konversi serikat yang mendasar dari S menjadi T diikuti dengan pembungkusan dari T ke T?
Resolusi:
Disetujui.
[Diselesaikan] Konversi yang diangkat
Apakah kita ingin menyesuaikan bagian Konversi yang diangkat untuk mendukung konversi serikat pekerja yang diangkat? Saat ini mereka tidak diizinkan:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(int? x)
{
return x; // error CS0029: Cannot implicitly convert type 'int?' to 'S1'
}
static S1? Test2(int? y)
{
return y; // error CS0029: Cannot implicitly convert type 'int?' to 'S1?'
}
}
Resolusi:
Tidak ada konversi serikat pekerja yang diangkat untuk saat ini. Beberapa catatan dari diskusi:
Analogi untuk konversi yang ditentukan pengguna memecah sedikit di sini. Dalam serikat umum dapat berisi nilai null yang masuk. Tidak jelas apakah pengangkatan harus membuat instans jenis serikat dengan
nullnilai yang disimpan di dalamnya, atau apakah harus membuatnullnilaiNullable<Union>.
[Diselesaikan] Memblokir konversi union dari instans jenis dasar?
Seseorang mungkin menemukan perilaku saat ini membingungkan:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(System.ValueType x)
{
}
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(System.ValueType x)
{
return x; // Union conversion
}
static S1 Test2(System.ValueType y)
{
return (S1)y; // Unboxing conversion
}
}
Perhatikan, bahasa secara eksplisit melarang pendeklarasikan konversi yang ditentukan pengguna dari jenis dasar. Oleh karena itu, mungkin membuat keheningan untuk tidak mengizinkan konversi serikat seperti itu.
Resolusi:
Jangan lakukan sesuatu yang istimewa untuk saat ini. Skenario generik tetap tidak dapat dilindungi sepenuhnya.
[Diselesaikan] Memblokir konversi union dari instans jenis antarmuka?
Seseorang mungkin menemukan perilaku saat ini membingungkan:
struct S1 : I1, System.Runtime.CompilerServices.IUnion
{
public S1(I1 x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
interface I1 { }
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(I1 x) => throw null;
public S2(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class C3 : System.Runtime.CompilerServices.IUnion
{
public C3(I1 x) => throw null;
public C3(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(I1 x)
{
return x; // Union conversion
}
static S1 Test2(I1 x)
{
return (S1)x; // Unboxing
}
static S2 Test3(I1 x)
{
return x; // Union conversion
}
static S2 Test4(I1 x)
{
return (S2)x; // Union conversion
}
static C3 Test3(I1 x)
{
return x; // Union conversion
}
static C3 Test4(I1 x)
{
return (C3)x; // Reference conversion
}
}
Perhatikan, bahasa secara eksplisit melarang pendeklarasikan konversi yang ditentukan pengguna dari jenis dasar. Oleh karena itu, mungkin membuat keheningan untuk tidak mengizinkan konversi serikat seperti itu.
Resolusi:
Jangan lakukan sesuatu yang istimewa untuk saat ini. Skenario generik tetap tidak dapat dilindungi sepenuhnya.
Namespace antarmuka IUnion
Berisi namespace untuk IUnion antarmuka tetap tidak ditentukan. Jika niatnya adalah untuk menyimpannya di global namespace layanan, Mari kita sebutkan secara eksplisit.
Proposal: Jika ini adalah sesuatu yang diabaikan, kita dapat menggunakan System.Runtime.CompilerServices namespace layanan.
Kelas sebagai Union jenis
[Diselesaikan] Memeriksa instans itu sendiri untuk null
Jika jenis serikat adalah jenis kelas, nilainya mungkin null. Lalu bagaimana dengan pemeriksaan null?
Pola null telah dipilih bersama untuk memeriksa Value properti, jadi bagaimana Anda memeriksa bahwa serikat itu sendiri tidak null?
Contohnya:
- Ketika
Sadalah struct,s is nulluntuk nilaitrueS?hanya ketikasitu sendiri adalahnull.UnionKetikaCadalahUnionkelas,c is nulluntuk nilaiC?adalahfalseketikacitu sendiri adalahnull, tetapi ketikacitutruesendiri bukannulldanc.Valueadalahnull.
Berikut adalah contoh lain:
class C1 : IUnion
{
private readonly object? _value;
public C1(){}
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object? IUnion.Value => _value;
}
class Program
{
static int Test1(C1? u)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
// This is very confusing, the switch expression is indeed not exhaustive (u itself is not
// checked for null), but there is a case 'null => 3' in the switch expression.
// It looks like the only way to shut off the warning is to use 'case _'. Adding it removes
// all benefits of exhaustiveness checking, any union case could be missing and there would
// be no diagnostic about that.
return u switch { int => 1, string => 2, null => 3 };
}
}
Bagian desain ini jelas dioptimalkan di sekitar harapan bahwa jenis serikat adalah struktur. Beberapa opsi:
- Sayang sekali. Gunakan
==untuk pemeriksaan null Anda alih-alih kecocokan pola. -
nullBiarkan pola (dan cek null implisit dalam pola lain) berlaku untuk nilai gabungan dan propertinyaValue:u is null ==> u == null || u.Value == null. - Larang kelas menjadi tipe serikat!
[Diselesaikan] Berasal dari Union kelas
Ketika kelas menggunakan Unionkelas sebagai kelas dasarnya, sesuai dengan spesifikasi saat ini, kelas tersebut menjadi Unionkelas itu sendiri. Ini terjadi karena secara otomatis "mewarisi" implementasi IUnion antarmuka, tidak diharuskan untuk menerapkannya kembali. Pada saat yang sama, konstruktor dari jenis turunan menentukan kumpulan jenis dalam baru Unionini . Sangat mudah untuk mendapatkan perilaku bahasa yang sangat aneh di sekitar dua kelas:
class C1 : IUnion
{
private readonly object _value;
public C1(long x) { _value = x; }
public C1(string x) { _value = x; }
object IUnion.Value => _value;
}
class C2(int x) : C1(x);
class Program
{
static int Test1(C1 u)
{
// Good
return u switch { long => 1, string => 2, null => 3 };
}
static int Test2(C2 u)
{
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'long'.
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'string'.
return u switch { long => 1, string => 2, null => 3 };
}
}
Beberapa opsi:
Ubah saat jenis kelas adalah
Unionjenis. Misalnya, kelas adalahUnionjenis ketika semua benar:-
sealedIni karena jenis turunan tidak akan dianggap sebagaiUnionjenis, memungkinkan yang membingungkan. - Tidak satu pun dari basisnya mengimplementasikan
IUnion
Ini masih belum sempurna. Aturannya terlalu halang. Sangat mudah untuk membuat kesalahan. Tidak ada diagnostik pada deklarasi, tetapi
Unionpencocokan tidak berfungsi.-
Melarang kelas menjadi jenis union.
[Diselesaikan] Operator is-type
Operator is-type ditentukan sebagai pemeriksaan jenis runtime. Secara sintis terlihat sangat mirip pola jenis, tetapi tidak. Oleh karena itu, pencocokan khusus Uniontidak akan digunakan, yang dapat menyebabkan kebingungan pengguna.
struct S1 : IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
return u is int; // warning CS0184: The given expression is never of the provided ('int') type
}
static bool Test2(S1 u)
{
return u is string and ['1', .., '2']; // Good
}
}
Jika terjadi penyatuan rekursif, pola jenis mungkin tidak memberikan peringatan, tetapi masih tidak akan melakukan apa yang mungkin dilakukan pengguna.
Resolusi: Harus bekerja sebagai pola jenis.
Pola daftar
Pola daftar selalu gagal dengan Union pencocokan:
struct S1 : IUnion
{
private readonly object _value;
public S1(int[] x) { _value = x; }
public S1(string[] x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
// error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found.
// error CS0021: Cannot apply indexing with [] to an expression of type 'object'
return u is [10];
}
}
static class Extensions
{
extension(object o)
{
public int Length => 0;
}
}
Pertanyaan lain
- Baik penggunaan konstruktor dalam konversi serikat pekerja dan penggunaan
TryGetValue(...)dalam pencocokan pola gabungan ditentukan untuk menjadi mudah ketika beberapa konstruktor berlaku: Mereka hanya akan memilih satu. Ini seharusnya tidak penting sesuai aturan kesejahteraan, tetapi apakah kita nyaman dengan itu? - Spesifikasi secara halus bergantung pada implementasi
IUnion.Valueproperti daripada properti apa pun yangValueditemukan pada jenis serikat itu sendiri. Ini dimaksudkan untuk memberikan fleksibilitas yang lebih besar untuk jenis yang ada (yang mungkin memiliki properti mereka sendiriValueuntuk penggunaan lain) untuk mengimplementasikan pola. Tetapi canggung, dan tidak konsisten dengan bagaimana anggota lain ditemukan dan digunakan langsung pada jenis serikat. Haruskah kita membuat perubahan? Beberapa opsi lainnya:- Mengharuskan jenis serikat untuk mengekspos properti publik
Value. - Lebih suka properti publik
Valuejika ada, tetapi kembali keIUnion.Valueimplementasi jika tidak (miripGetEnumeratordengan aturan).
- Mengharuskan jenis serikat untuk mengekspos properti publik
- Sintaks deklarasi serikat yang diusulkan tidak dicintai secara universal, terutama dalam hal mengekspresikan jenis kasus. Alternatif sejauh ini juga bertemu dengan kritik, tetapi ada kemungkinan kita akan akhirnya membuat perubahan. Beberapa kekhawatiran utama disuarakan tentang yang saat ini:
- Koma sebagai pemisah antara jenis kasus mungkin tampaknya menyiratkan bahwa pesanan penting.
- Daftar yang dikurung terlihat terlalu banyak seperti konstruktor utama (meskipun tidak memiliki nama parameter).
- Terlalu berbeda dari enum, yang memiliki "kasus" mereka dalam kurung kurawal.
- Meskipun deklarasi serikat menghasilkan struktur dengan satu bidang referensi, deklarasi tersebut masih agak rentan terhadap perilaku tak terduga ketika digunakan dalam konteks bersamaan. Misalnya, jika anggota fungsi yang ditentukan pengguna mendereferensikan
thislebih dari sekali, variabel yang berisi mungkin telah ditetapkan kembali secara keseluruhan oleh utas lain di antara kedua akses. Pengkompilasi dapat menghasilkan kode untuk disalinthiske lokal jika diperlukan. Haruskah? Secara umum, tingkat ketahanan konkurensi apa yang diinginkan dan dapat dicapai secara wajar?
C# feature specifications