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.
Note
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 menunjukkan jenis yang dapat dimiliki nilai kontennya.
-
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 penyatuan diidentifikasi sebagai sekumpulan jenis parameter konstruktor serikat pekerja ini.
// 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 parameter metode pabrik ini.
// 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 mengembalikan dan mengambil parameter keluar tunggal dari jenis yang dapat dikonversi identitas ke jenis kasus atau secara opsional jenis nilai yang mendasarbool, jika jenis kasus adalah jenis nilai nullable.
// 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 union Value adalah nilai non-null 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.
[Union]
struct S1
{
public S1(int x) => ...
public S1(string x) => ...
public object 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.
[Union]
struct S2
{
public S2(int x) => ...
public S2(string x) => ...
public object 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 input pola adalah jenis gabungan atau dari jenis gabungan yang dapat diubah ke null, nilai nullable dan isi nilai union yang mendasar mungkin "dibongkar", tergantung pada polanya.
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
Valueproperti pada serikat masuk.
Operator is-type yang diterapkan ke jenis gabungan memiliki arti yang sama dengan pola jenis yang diterapkan ke jenis gabungan.
Pola yang dikurung
parenthesized_pattern
: '(' pattern ')'
;
Pola yang mendasar diterapkan ke nilai input dari pola yang diapit kurung dan nilai output yang dihasilkan adalah nilai output dari pola yang diapit tanda kurung.
Pola var
Lihat Pola var.
Nilai serikat yang mendasar tidak dibungkus. Ini berlaku untuk semua bentuk var pattern.
if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet`
Pola jenis
Lihat Pola jenis.
type_pattern
: type
;
Mengingat nilai input pola e dari jenis serikat atau dari jenis gabungan yang dapat diubah ke null, pengujian dilakukan terhadap instans gabungan dan nilai serikat. Jika salah satu pengujian berhasil, maka pola berhasil. Instans union selalu diuji terlebih dahulu dan kemudian nilai union hanya jika pengujian union gagal.
Union Valuekompatibel dengan pola dengan jenis ketika setidaknya ada satu jenis elemen yang kompatibeldengan pola.
Jika jenis serikat itu sendiri kompatibel dengan pola yang kompatibel dengan jenis dan gabungan Value dengan jenis, type_pattern setara dengan , dengan asumsi type or { Value: type } bahwa formulir ini tidak menggunakan perlakuan khusus apa pun untuk serikat.
Nilai output type_pattern dalam hal ini adalah instans jenis union yang dipersempit untuk mengetik jika berhasil dicocokkan dengan jenis .
Jika tidak, itu adalah serikat Value dipersempit ke type.
Jika jenis serikat itu sendiri bukan pola yang kompatibel dengan jenis dan gabungan Value dengan jenis, type_pattern setara dengan , dengan asumsi { Value: type } bahwa formulir ini tidak menggunakan perlakuan khusus apa pun untuk serikat.
Nilai output type_pattern dalam hal ini adalah penyatuan Value yang dipersempit ke type.
Jika jenis serikat itu sendiri kompatibel dengan pola yang kompatibel dengan jenis dan gabungan tidak Value dengan jenis, type_pattern setara dengan , dengan asumsi type bahwa formulir ini tidak menggunakan perlakuan khusus untuk serikat.
Nilai output type_pattern dalam hal ini adalah instans jenis union yang dipersempit ke jenis.
Jika jenis gabungan itu sendiri tidak kompatibel dengan pola dan gabungan Value tidak kompatibel dengan jenis, type_pattern tidak berlaku untuk nilai input dan kesalahan kompilasi dilaporkan.
union Pet(Cat, Dog);
Pet? p = new Cat(...);
p is Pet // true, since p.Value is a Pet, output value is p.Value
p is Cat // true, since p.Value.Value is a Cat, output value is (Cat)p.Value.Value
Pola deklarasi
Lihat Pola deklarasi.
declaration_pattern
: type simple_designation
;
simple_designation
: discard_designation
| single_variable_designation
;
discard_designation
: '_'
;
single_variable_designation
: identifier
;
declaration_pattern setara type and var simple_designationdengan .
record Cat(...) : ICat;
union Pet(Cat, Dog) : IPet;
Pet p = new Cat(...);
p is IPet ip // true, since Pet implements IPet, ip is (IPet)p
p is ICat c // true, since p.Value is a Cat and Cat implements ICat, c is (ICat)p.Value
Pola properti
Lihat Pola properti.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Jika bagian jenisproperty_pattern hilang, nilai serikat yang mendasar tidak dibungkus dan pencocokan dilakukan hanya terhadap instans union.
Jika bagian jenisproperty_pattern ada, polanya setara dengan type and property_subpattern simple_designation?.
record Cat(string Name);
union Pet(Cat, Dog);
Pet p = new Cat(Name: "Fido");
p is { Name: "Fido" } // error: Pet has no 'Name'; applied to p
p is { Value: Cat } // true; applied to p
p is Pet { Value: Cat } // true; applied to p
p is Cat { Name: "Fido" } // true; applied to p.Value
p is {} // true; applied to p and always true for struct union
Dengan asumsi bahwa Pet adalah serikat kelas dan I1 merupakan antarmuka dengan properti Name string jenis:
Pet p = GetPet();
// true when
// - p implements I1 and its I1.Name is "Fido", or
// - when p doesn't implement I1, p.Value implements I1 and its I1.Name is "Fido"
// The output value for the pattern is either (I1)p or (I1)p.Value
p is I1 { Name: "Fido" }
Pola posisional
Lihat Pola posisi.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern (',' subpattern)*
;
subpattern
: pattern
| subpattern_name ':' pattern
;
subpattern_name
: identifier
| subpattern_name '.' identifier
;
Jika bagian jenispositional_pattern hilang, nilai serikat yang mendasar tidak dibungkus dan pencocokan dilakukan hanya terhadap instans union.
Jika bagian jenispositional_pattern ada, polanya setara dengan type and '(' subpatterns? ')' property_subpattern? simple_designation?.
record Cat(string Name);
// a union type with its own deconstruct
union Pet(Cat, Dog) { public void Deconstruct(out object value) { value = this.Value; }}
Pet p = new Cat(Name: "Fido");
p is ("Fido") // false: applied to p
p is (Cat) // true: applied to p
p is Pet (Cat) // true: applied to p
p is Cat ("Fido") // true; applied to p.Value
Dengan asumsi itu Pet adalah serikat kelas dan I1 merupakan antarmuka dengan metode Deconstruct yang sesuai:
Pet p = GetPet();
// true when
// - p implements I1 and its deconstruction is ("Fido"), or
// - when p doesn't implement I1, p.Value implements I1 and its deconstruction is ("Fido")
// The output value for the pattern is either (I1)p or (I1)p.Value
p is I1 ("Fido")
Pola konstanta
Lihat Pola konstanta.
constant_pattern
: constant_expression
;
Ketika constant_expression adalah null.
Jika nilai input adalah jenis serikat kelas, maka nullconstant_pattern akan berhasil terlepas dari apakah nilai serikat kerja itu sendiri atau null nilai yang terkandung adalah null:
if (result is null) { ... } // if (result == null || result.Value == null)
union class StringOrInt(string, int?); // not actual syntax
StringOrInt? n1 = null;
StringOrInt n2 = (int?)null;
n1 is null // true : n1 *is* null
n2 is null // true : n2.Value *is* null
Demikian pula, jika nilai input adalah jenis nilai nullable (membungkus jenis gabungan 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)
union StringOrInt(string, int?);
StringOrInt? n1 = null;
StringOrInt? n2 = (int?)null;
n1 is null // true : n1 *is* null
n2 is null // true : n2.Value *is* null
Jika nilai input adalah jenis struct union, maka nullconstant_pattern akan berhasil ketika nilai union yang terkandung adalah null:
if (result is null) { ... } // if (result.Value == null)
Nilai output dari nullconstant_pattern adalah nilai inputnya
Ketika constant_expression bukan null.
constant_patternformulir ini hanya akan berhasil ketika instans serikat itu sendiri tidak null dan Value cocok dengan constant_expression.
if (result is 1) { ... } // if (result != null && result.Value is 1)
Nilai output constant_pattern kemudian disempitkan Value ke jenis constant_expression.
union StringOrInt(string, int);
StringOrInt? s = 10;
s is 10 // true : s.Value.Value is an int and the int has the value 10, output value is (int)s.Value.Value
Pola buang
Lihat Membuang pola.
Nilai serikat yang mendasar tidak dibungkus.
Pola relasional
Lihat Pola relasional.
relational_pattern
: '<' relational_expression
| '<=' relational_expression
| '>' relational_expression
| '>=' relational_expression
;
relational_pattern akan berhasil hanya ketika instans union itu sendiri tidak null dan cocok dengan polanyaValue.
if (result is > 1) { ... } // if (result != null && result.Value is > 1)
Nilai output relational_pattern kemudian disempitkan Value ke jenis relational_expression.
union StringOrInt(string, int);
StringOrInt? s = 10;
s is > 9 // true : s.Value.Value is an int and the int has the value > 9, output value is (int)s.Value.Value
Pola daftar
Lihat Pola daftar.
Nilai serikat yang mendasar tidak dibungkus.
Pola logis
Lihat Pola logis.
logical_pattern
: disjunctive_pattern
;
disjunctive_pattern
: disjunctive_pattern 'or' conjunctive_pattern
| conjunctive_pattern
;
conjunctive_pattern
: conjunctive_pattern 'and' negated_pattern
| negated_pattern
;
negated_pattern
: 'not' negated_pattern
| primary_pattern
;
Untuk negated_patternnegated_pattern diterapkan ke nilai input yang sama dan kemudian hasilnya dinegasikan. Nilai output dari negated_pattern adalah nilai inputnya.
Dalam pola and , nilai input pola kiri adalah nilai input dari and.
Nilai input pola kanan adalah nilai output dari pola andkiri .
Nilai output pola and adalah nilai output dari pola yang tepat.
Dalam pola or , nilai input pola kiri adalah nilai input dari or.
Nilai input pola yang tepat adalah nilai input dari or.
Nilai output pola or adalah nilai inputnya, jenis yang dipersempit adalah jenis umum dari jenis yang dipersempit untuk sumber nilai subpattern jika jenis umum tersebut ada.
Perhatikan bahwa unwrapping instans union mengubah sumber nilai.
union Pet(Cat, Dog);
GetPet() switch
{
var pet and not null => ... // 'var pet' applies to the incoming 'Pet' as does 'not' and 'null' to its 'Value'
not null and var value => ... // 'not' applies to the incoming 'Pet', 'null' applies to the its 'Value'.
// 'var value' applies to the incoming 'Pet' because the left branch in this case
// (`not null`) does not change the incoming value for the right branch.
var pet and Dog => ... // 'var pet' applies to the incoming 'Pet' and 'Dog' to its 'Value'
Dog and { Name: "Dog" } => ... // 'Dog' applies to the incoming 'Pet''s `Value` and it is changing the incoming value
// for the right branch to the `Dog` instance. Therefore, the property pattern on the right
// is applied to that `Dog` instance
}
Dengan asumsi bahwa Pet adalah serikat kelas dan I1 merupakan antarmuka dengan properti Name string jenis:
Pet p = GetPet();
// The left pattern is true when
// - p implements I1, or
// - when p doesn't implement I1 and p.Value implements I1
// The output value for the left pattern is either (I1)p or (I1)p.Value,
// which also serves as in input value for the right pattern.
// The right pattern is true when I1.Name is "Fido" for its input value.
// The output value for the entire `and` pattern is either (I1)p or (I1)p.Value.
p is I1 and { Name: "Fido" }
union StringOrInt(string, int);
GetStringOrInt() switch
{
1 or 2 => ... // '1' applies to the incoming 'StringOrInt''s `Value`
// '2' also applies to the incoming 'StringOrInt''s `Value
// The output value for `or` pattern is the incoming 'StringOrInt' instance
3 or "a" => ... // '3' applies to the incoming 'StringOrInt''s `Value`
// "a" also applies to the incoming 'StringOrInt''s `Value
// The output value for `or` pattern is the incoming 'StringOrInt' instance
}
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:
- Status null default properti jenis
Valueunion adalah "mungkin null" jika status null default dari salah satu jenis kasus adalah "mungkin null". Jika tidak, status null default adalah "bukan null". - Ketika anggota pembuatan serikat dipanggil (secara eksplisit atau melalui konversi serikat pekerja), properti serikat
Valuepekerja baru mendapatkan 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.
Syntax
Deklarasi serikat memiliki nama dan daftar jenis konstruktor serikat pekerja.
union_declaration
: attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list?
'(' case_types ')' struct_interfaces? type_parameter_constraints_clause*
(`{` struct_member_declaration* `}` | ';')
;
case_types
: type (',' type)*
;
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.
case_types dapat berupa jenis apa pun yang dikonversi ke object, misalnya, antarmuka, parameter jenis, jenis nullable, dan serikat 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>);
Menurunkan
Deklarasi serikat diturunkan ke deklarasi struct dengan
- atribut, pengubah, nama, parameter jenis, dan batasan yang sama,
- implementasi implisit dari
IUnion, - properti
public object? Value { get; }otomatis, - konstruktor publik untuk setiap case_types,
- anggota dalam badan deklarasi serikat.
Ini adalah kesalahan bagi anggota yang dideklarasikan pengguna untuk berkonflik dengan anggota yang dihasilkan.
Contoh:
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
Haruskah pengkompilasi melaporkan kesalahan untuk deklarasi gabungan kustom di sumber yang kehilangan set API minimal?
Pembuatan dan
Valueproperti wajib, dan secara kolektif disebut sebagai pola serikat dasar.
Jenis serikat harus memiliki setidaknya satu anggota pembuatan serikat ...
Apakah ini berarti pengkompilasi harus melaporkan kesalahan untuk deklarasi gabungan kustom di sumber yang:
- Tidak memiliki properti Nilai yang sesuai
- Atau tidak memiliki setidaknya satu anggota pembuatan serikat
Atau apakah ini hanya informasi untuk pengguna bahwa jenis serikat pekerja yang terbentuk dengan baik harus memiliki anggota? Jika tidak, hal-hal mungkin tidak berfungsi.
Haruskah properti Nilai langsung cocok mengikuti aturan Union?
Apakah pengetahuan tentang jenis kasus diterapkan ke pola atau tidak?
[System.Runtime.CompilerServices.Union]
struct S1
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string? x) { _value = x; }
public object Value => _value;
}
class Program
{
static int Test1(object u)
{
// Should we get:
// warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
// For example, the pattern 'S1{ Value: _ }' is not covered.
return u switch { S1 { Value: int } => 1, S1 { Value: string } => 2, not S1 => -100 };
}
static bool Test2(object u)
{
// Should we get:
// error CS8121: An expression of type 'S1' cannot be handled by a pattern of type 'long'.
return u is S1 { Value: long };
}
}
[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 struct/record structdi 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.
[Diselesaikan] 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?
Resolusi: Pengkompilasi harus secara diam-diam mengabaikan API tersebut tanpa melaporkan kesalahan atau diagnostik, selaras dengan seni sebelumnya untuk skenario pengoptimalan.
[Diselesaikan] Bagaimana jika jenis untuk deklarasi serikat tidak ada
Apa yang terjadi jika UnionAttribute, IUnion atau IUnion<TUnion> hilang? Kesalahan? Mensintesis? Sesuatu yang lain?
Resolusi: Pengkompilasi tidak boleh mensintesis jenis ini dan pengguna harus menyediakannya secara eksplisit, baik dengan merujuk rakitan atau mendefinisikannya secara lokal.
[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 HasValue yang sesuaiTryGetValue.
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.
[Usang] 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?
[Diselesaikan] 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
}
}
Resolusi: Dikonfirmasi
[Diselesaikan] Haruskah atribut pasca-kondisi memengaruhi nullability default instans Union?
Status null default properti jenis
Valueunion adalah "mungkin null" jika status null default dari salah satu jenis kasus adalah "mungkin null". Jika tidak, status null default adalah "bukan 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 }; //
}
}
Resolusi: Tetap pada spesifikasi saat ini untuk saat ini - hanya lihat jenis
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,Unionuntuk nilais is nullS?hanya ketikatrueitu sendiri adalahs.nullKetikaCadalahUnionkelas,c is nulluntuk nilaiC?adalahfalseketikacitu sendiri adalahnull, tetapi ketikatrueitucsendiri 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.
[Diselesaikan] 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;
}
}
Resolusi: Bukan lagi kasusnya. Blok ekstensi dapat mengaktifkan skenario pola daftar untuk jenis gabungan dengan menambahkan API yang hilang untuk object.
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