Bagikan melalui


Serikat-serikat

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 HasValue jenis bool dengan aksesor publik get . Ini mungkin secara opsional memiliki atau initset aksesor, yang dapat memiliki aksesibilitas apa pun dan tidak digunakan oleh pengkompilasi.
  • Metode TryGetValue untuk setiap jenis kasus. Metode mengembalikan bool dan 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.

  • KedengaranValue: 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, Value properti akan cocok dengan jenis kasus tersebut atau null. Jika nilai union dibuat dari null nilai, Value properti akan menjadi null.
  • 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 HasValue anggota akses non-tinju dan TryGetValue , jika ada, dapat diamati setara dengan pemeriksaan terhadap Value properti 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 , jika TryGetValue(S value) metode tersedia, dan ada identitas, atau konversi referensi/tinju implisit dari T ke S, 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 dari T ke S bukan 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 , nulljika HasValue properti tersedia, properti tersebut digunakan untuk memeriksa apakah nilai gabungan null.
  • Jika tidak, pola diterapkan ke hasil mengakses IUnion.Value properti 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 Value mendapatkan status null dari nilai masuk.
  • Ketika pola HasValue akses non-tinju atau TryGetValue(...) digunakan untuk mengkueri konten jenis gabungan (secara eksplisit atau melalui pencocokan pola), itu berdampak pada Valuestatus nullability dengan cara yang sama seperti jika Value telah dicentang secara langsung: Status null Value menjadi "tidak null" pada true cabang.

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 TryGetValue untuk setiap jenis kasus. Metode mengembalikan bool dan 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 Value adalah "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 , jika TryGetValue(S value) metode tersedia, dan ada konversi implisit dari T ke S, 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 jenis Tkasus .

Akan lebih baik untuk memiliki jawaban eksplisit.

Resolusi: Hanya identitas implisit, atau referensi, atau konversi tinju yang dipertimbangkan

TryGetValue dan analisis nullable

Ketika pola HasValue akses non-tinju atau TryGetValue(...) digunakan untuk mengkueri konten jenis gabungan (secara eksplisit atau melalui pencocokan pola), itu berdampak pada Valuestatus nullability dengan cara yang sama seperti jika Value telah dicentang secara langsung: Status null Value menjadi "tidak null" pada true cabang.

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 jenis Tkasus .

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 Value adalah "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 null sebagai Value.

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 Value adalah "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 null nilai yang disimpan di dalamnya, atau apakah harus membuat null nilai Nullable<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 S adalah struct, s is null untuk nilai trueS?hanya ketika s itu sendiri adalah null.Union Ketika C adalah Union kelas, c is null untuk nilai C?adalah falseketika c itu sendiri adalah null, tetapi ketika c itu true sendiri bukan nulldan c.Value adalah null.

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.
  • null Biarkan 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 Union jenis. Misalnya, kelas adalah Union jenis ketika semua benar:

    • sealed Ini karena jenis turunan tidak akan dianggap sebagai Unionjenis, 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 Union pencocokan 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.Value properti daripada properti apa pun yang Value ditemukan pada jenis serikat itu sendiri. Ini dimaksudkan untuk memberikan fleksibilitas yang lebih besar untuk jenis yang ada (yang mungkin memiliki properti mereka sendiri Value untuk 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 Value jika ada, tetapi kembali ke IUnion.Value implementasi jika tidak (mirip GetEnumerator dengan aturan).
  • 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 this lebih dari sekali, variabel yang berisi mungkin telah ditetapkan kembali secara keseluruhan oleh utas lain di antara kedua akses. Pengkompilasi dapat menghasilkan kode untuk disalin this ke lokal jika diperlukan. Haruskah? Secara umum, tingkat ketahanan konkurensi apa yang diinginkan dan dapat dicapai secara wajar?