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.
Jenis union mewakili nilai yang bisa menjadi salah satu dari beberapa jenis kasus. Serikat pekerja menyediakan konversi implisit dari setiap jenis kasus, pencocokan pola lengkap, dan pelacakan nullability yang ditingkatkan.
union Gunakan kata kunci untuk mendeklarasikan jenis serikat:
public union Pet(Cat, Dog, Bird);
Deklarasi ini membuat serikat Pet dengan tiga jenis kasus: Cat, Dog, dan Bird. Anda dapat menetapkan nilai jenis kasus apa pun ke Pet variabel. Pengkompilasi memastikan bahwa switch ekspresi mencakup semua jenis kasus.
Referensi bahasa C# mendokumentasikan versi bahasa C# yang paling baru dirilis. Ini juga berisi dokumentasi awal untuk fitur dalam pratinjau publik untuk rilis bahasa yang akan datang.
Dokumentasi mengidentifikasi fitur apa pun yang pertama kali diperkenalkan dalam tiga versi terakhir bahasa atau dalam pratinjau publik saat ini.
Petunjuk / Saran
Untuk menemukan kapan fitur pertama kali diperkenalkan di C#, lihat artikel tentang riwayat versi bahasa C#.
Nyatakan serikat ketika nilai harus tepat salah satu dari sekumpulan jenis tetap dan Anda ingin pengkompilasi memberlakukan bahwa setiap kemungkinan ditangani. Skenario umum meliputi:
-
Hasil kesalahan: Metode mengembalikan nilai keberhasilan atau nilai kesalahan, dan pemanggil harus menangani keduanya. Serikat seperti
union Result(Success, Error)membuat serangkaian hasil menjadi eksplisit. -
Pengiriman pesan atau perintah: Sistem memproses sekumpulan jenis pesan tertutup. Gabungan memastikan jenis pesan baru menghasilkan peringatan waktu kompilasi di setiap
switchyang belum menanganinya. - Mengganti antarmuka penanda atau kelas dasar abstrak: Jika Anda menggunakan antarmuka atau kelas abstrak semata-mata untuk jenis grup untuk pencocokan pola, serikat pekerja memberi Anda pemeriksaan kelelahan tanpa memerlukan warisan atau anggota bersama.
Serikat pekerja berbeda dari deklarasi jenis lain dengan cara penting:
-
classTidak seperti ataustruct, serikat tidak menentukan anggota data baru. Sebaliknya, ia menyusun jenis yang ada ke dalam sekumpulan alternatif tertutup. -
interfaceTidak seperti , serikat tertutup—Anda menentukan daftar lengkap jenis kasus dalam deklarasi, dan pengkompilasi menggunakan daftar tersebut untuk pemeriksaan kelelahan. -
recordTidak seperti , serikat tidak menambahkan perilaku kesetaraan, kloning, atau dekonstruksi. Serikat berfokus pada "kasus mana itu?" daripada "bidang apa yang dimilikinya?"
Deklarasi serikat
Deklarasi serikat kerja menentukan nama dan daftar jenis kasus:
public union Pet(Cat, Dog, Bird);
Jenis kasus dapat berupa jenis apa pun yang dikonversi ke object, termasuk kelas, struct, antarmuka, parameter jenis, jenis nullable, dan serikat lainnya. Contoh berikut menunjukkan kemungkinan jenis kasus yang berbeda:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);
public union IntOrString(int, string);
Ketika jenis kasus adalah jenis nilai (seperti int), nilai dikotak saat disimpan di properti gabungan Value . Serikat buruh menyimpan kontennya sebagai referensi tunggal object? .
Deklarasi serikat dapat mencakup isi dengan anggota tambahan, sama seperti struktur, tunduk pada beberapa batasan. Deklarasi serikat tidak dapat menyertakan bidang instans, properti otomatis, atau peristiwa seperti bidang. Anda juga tidak dapat mendeklarasikan konstruktor publik dengan satu parameter, karena kompilator menghasilkan konstruktor tersebut sebagai anggota pembuatan serikat pekerja. Gabungan berikut Length menambahkan TotalMeters properti yang menggunakan pencocokan pola untuk menangani setiap jenis kasus, bersama dengan Add metode yang menggabungkan dua panjang:
public record class Meters(double Value);
public record class Feet(double Value);
public union Length(Meters, Feet)
{
public double TotalMeters => this switch
{
Meters m => m.Value,
Feet f => f.Value * 0.3048,
_ => throw new InvalidOperationException("The Length has no value."),
};
public Length Add(Length other) => new Meters(TotalMeters + other.TotalMeters);
}
Konversi serikat pekerja
Konversi serikat implisit ada dari setiap jenis kasus ke jenis serikat:
static void BasicConversion()
{
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // output: Dog { Name = Rex }
Pet pet2 = new Cat("Whiskers");
Console.WriteLine(pet2.Value); // output: Cat { Name = Whiskers }
}
Konversi gabungan berfungsi dengan memanggil konstruktor yang dihasilkan yang sesuai. Jika operator konversi implisit yang ditentukan pengguna ada untuk jenis yang sama, operator yang ditentukan pengguna lebih diprioritaskan daripada konversi serikat pekerja. Jika lebih dari satu jenis kasus sama-sama berlaku untuk nilai sumber, konversi gabungan ambigu, dan pengompilasi melaporkan kesalahan. Untuk detail tentang prioritas konversi, lihat spesifikasi fitur.
Konversi union ke union struct nullable (T?) juga berfungsi ketika T adalah jenis union:
static void NullableUnionExample()
{
Pet? maybePet = new Dog("Buddy");
Pet? noPet = null;
Console.WriteLine(Describe(maybePet)); // output: Dog: Buddy
Console.WriteLine(Describe(noPet)); // output: no pet
static string Describe(Pet? pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
}
Pencocokan pola gabungan
Ketika Anda mencocokkan pola pada jenis serikat, pola umumnya berlaku untuk properti serikat Value , bukan nilai gabungan itu sendiri. Perilaku "unwrapping" ini berarti penyatuan transparan terhadap pencocokan pola:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Tiga pola adalah pengecualian untuk aturan ini: pola buang _ , var pola, dan not pola berlaku untuk nilai gabungan itu sendiri, bukan propertinya Value . Gunakan var untuk mengambil nilai union saat GetPet() mengembalikan Pet? (Nullable<Pet>):
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
Dalam pola logis, setiap cabang mengikuti aturan pembungkusan satu per satu. Cabang kiri and pola dapat mengubah nilai masuk yang dilihat cabang kanan.
not Karena pola berlaku untuk nilai union masuk daripada Value, leading not null tidak membongkar nilai untuk cabang yang mengikutinya:
GetPet() switch
{
// 'var pet' captures the Pet?; 'not null' applies to the Pet? value (not pet.Value)
var pet and not null => ...,
// 'not null' doesn't unwrap to Pet, so 'var value' still captures the Pet?
not null and var value => ...,
}
Nota
Karena pola berlaku untuk Value, pola seperti pet is Pet biasanya tidak cocok, Pet karena diuji terhadap konten serikat, bukan serikat itu sendiri.
Pencocokan null
Untuk penyatuan struct, null pola memeriksa apakah Value null:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Untuk serikat berbasis kelas, null berhasil ketika referensi serikat itu sendiri null atau propertinya Value null:
Result<string>? result = null;
if (result is null) { /* true — the reference is null */ }
Result<string> empty = new Result<string>((string?)null);
if (empty is null) { /* true — Value is null */ }
Untuk jenis struct union nullable (Pet?), null berhasil ketika pembungkus nullable tidak memiliki nilai atau ketika union Value yang mendasar adalah null.
Kelelahan serikat
Ekspresi switch lengkap saat menangani semua jenis kasus serikat. Pengkompilasi memperingatkan hanya jika jenis kasus tidak ditangani. Anda tidak perlu menyertakan pola buang (_) atau var pola untuk mencocokkan jenis apa pun ketika ekspresi pasti ditetapkan:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Jika status null properti serikat adalah Value "mungkin null", Anda juga harus menangani null untuk menghindari peringatan:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Situasi ini dapat muncul ketika union ekspresi adalah nilai default atau tidak pasti ditetapkan, seperti yang ditunjukkan dalam sampel sebelumnya.
Keterbatalan
Pengkompilasi melacak status null properti gabungan Value melalui aturan berikut:
- Status null default properti serikat adalah
Value"mungkin null" jika status null default dari jenis kasus apa pun adalah "mungkin null." Jika tidak, status null default adalah "bukan null." - Saat Anda membuat nilai union dari jenis kasus (melalui konstruktor atau konversi union),
Valuemendapatkan status null dari nilai masuk. - Saat pola
HasValueakses non-tinju atauTryGetValue(...)anggota mengkueri konten serikat, statusValuenull menjadi "tidak null" padatruecabang.
Jenis serikat kustom
Kompilator mengonversi union deklarasi menjadi struct deklarasi. Struktur ditandai dengan [System.Runtime.CompilerServices.Union] atribut dan mengimplementasikan IUnion antarmuka. Ini termasuk konstruktor publik dan konversi implisit untuk setiap jenis kasus bersama dengan Value properti. Bentuk yang dihasilkan dipendapatkan. Ini selalu merupakan struktur, selalu kotak kasus jenis nilai, dan selalu menyimpan konten sebagai object?.
Anda mungkin memerlukan perilaku yang berbeda jika Anda ingin menyesuaikan jenis yang ada, membuat serikat berbasis kelas, atau menggunakan strategi penyimpanan kustom, atau jika Anda memerlukan dukungan interop. Anda dapat membuat jenis serikat secara manual.
Kelas atau struktur apa pun dengan [Union] atribut adalah jenis gabungan jika mengikuti pola penyatuan dasar. Pola penyatuan dasar memerlukan:
- Atribut
[Union]pada jenis . - Satu atau beberapa konstruktor publik, masing-masing dengan satu menurut nilai atau
inparameter. Jenis parameter setiap konstruktor mendefinisikan jenis kasus. - Properti publik
Valueberjenisobject?(atauobject) dengangetaksesor.
Semua anggota serikat sebelumnya harus publik. Kompilator menggunakan anggota ini untuk menerapkan konversi serikat pekerja, pencocokan pola, dan pemeriksaan kelelahan. Anda juga dapat menerapkan pola akses non-tinju atau membuat jenis serikat berbasis kelas. Jenis serikat kustom Anda dapat menambahkan anggota tambahan.
Pengkompilasi mengasumsikan bahwa jenis serikat kustom memenuhi aturan perilaku ini:
-
Kedamaian:
Valueselalu mengembalikannullatau nilai dari salah satu jenis kasus - tidak pernah nilai dari jenis yang berbeda. Untuk penyatuan struct,defaultmenghasilkanValuedarinull. -
Stabilitas: Jika Anda membuat nilai serikat dari jenis kasus,
Valuecocok dengan jenis kasus tersebut (atau apakahnullinputnya adalahnull). - Kesetaraan pembuatan: Jika nilai secara implisit dapat dikonversi ke dua jenis kasus yang berbeda, kedua anggota pembuatan menghasilkan perilaku yang dapat diamati yang sama.
-
Konsistensi pola akses: Anggota
HasValuedanTryGetValue, jika ada, berperilaku setara dengan memeriksaValuesecara langsung.
Contoh berikut menunjukkan jenis serikat kustom:
[System.Runtime.CompilerServices.Union]
public struct Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }
public object? Value => _value;
}
public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
static void ManualUnionExample()
{
Shape shape = new Shape(new Circle(5.0));
var area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
};
Console.WriteLine($"{area:F2}"); // output: 78.54
}
Pola akses non-tinju
Jenis gabungan kustom dapat secara opsional menerapkan pola akses non-tinju untuk memungkinkan akses yang sangat ditik ke kasus jenis nilai tanpa tinju selama pencocokan pola. Pola ini memerlukan:
- Properti
HasValuejenisboolyang mengembalikantruejikaValuebukannull. - Metode
TryGetValueuntuk setiap jenis kasus yang mengembalikanbooldan memberikan nilai melaluioutparameter.TryGetValuetruemengembalikan hanya ketikaValueadalah nilai bukan null dari jenis kasus tersebut. Jenisoutparameter adalah identity-convertible ke jenis kasus, atau ke jenis nilai yang mendasar ketika jenis kasus adalah jenis nilai nullable.
[System.Runtime.CompilerServices.Union]
public struct IntOrBool : System.Runtime.CompilerServices.IUnion
{
private readonly int _intValue;
private readonly bool _boolValue;
private readonly byte _tag; // 0 = none, 1 = int, 2 = bool
public IntOrBool(int? value)
{
if (value.HasValue)
{
_intValue = value.Value;
_tag = 1;
}
}
public IntOrBool(bool? value)
{
if (value.HasValue)
{
_boolValue = value.Value;
_tag = 2;
}
}
public object? Value => _tag switch
{
1 => _intValue,
2 => _boolValue,
_ => null
};
public bool HasValue => _tag != 0;
public bool TryGetValue(out int value)
{
value = _intValue;
return _tag == 1;
}
public bool TryGetValue(out bool value)
{
value = _boolValue;
return _tag == 2;
}
}
static void NonBoxingExample()
{
IntOrBool val = new IntOrBool((int?)42);
var description = val switch
{
int i => $"int: {i}",
bool b => $"bool: {b}",
};
Console.WriteLine(description); // output: int: 42
}
Pengkompilasi lebih memilih TryGetValue daripada Value properti saat menerapkan pencocokan pola, yang menghindari jenis nilai tinju.
Penyedia anggota gabungan
Jenis gabungan dapat mendelegasikan anggota gabungannya ke antarmuka berlapis IUnionMembers . Ketika antarmuka ini ada, pengkompilasi mencari Create metode pabrik alih-alih konstruktor:
[System.Runtime.CompilerServices.Union]
public record class Outcome<T> : Outcome<T>.IUnionMembers
{
private readonly object? _value;
private Outcome(object? value) => _value = value;
public interface IUnionMembers
{
static Outcome<T> Create(T? value) => new(value);
static Outcome<T> Create(Exception? value) => new(value);
object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
Penyedia anggota serikat pekerja berguna ketika jenis serikat pekerja membutuhkan konstruktor privat atau ketika logika pembuatan memerlukan pola pabrik, seperti dengan record class jenis gabungan.
Jenis serikat berbasis kelas
Kelas juga dapat menjadi jenis serikat. Jenis penyatuan ini berguna ketika Anda memerlukan semantik referensi atau warisan:
[System.Runtime.CompilerServices.Union]
public class Result<T> : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Result(T? value) { _value = value; }
public Result(Exception? value) { _value = value; }
public object? Value => _value;
}
static void ClassUnionExample()
{
Result<string> ok = new Result<string>("success");
Result<string> err = new Result<string>(new InvalidOperationException("failed"));
Console.WriteLine(Describe(ok)); // output: OK: success
Console.WriteLine(Describe(err)); // output: Error: failed
static string Describe(Result<string> result) => result switch
{
string s => $"OK: {s}",
Exception e => $"Error: {e.Message}",
null => "null",
};
}
Untuk serikat berbasis kelas, null pola cocok dengan referensi null dan null Value.
Implementasi serikat
Jenis union mengandalkan UnionAttribute jenis dan IUnion di System.Runtime.CompilerServices namespace. Runtime mencakup jenis ini yang dimulai dengan .NET 11 Pratinjau 5:
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
Deklarasi gabungan yang dihasilkan oleh kompilator mengimplementasikan IUnion. Anda dapat memeriksa nilai union apa pun saat runtime dengan menggunakan IUnion:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
Ketika Anda mendeklarasikan union jenis, kompilator menghasilkan struct yang mengimplementasikan IUnion. Misalnya, Pet deklarasi (public union Pet(Cat, Dog, Bird);) menjadi setara dengan:
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public Pet(Bird value) => Value = value;
public object? Value { get; }
}
Spesifikasi bahasa C#
Untuk informasi selengkapnya, lihat spesifikasi fitur Unions .