聯合型別代表的值可以是多種案件類型之一。 聯合會提供每種案例類型的隱式轉換、窮盡模式匹配,以及增強的空可追溯性追蹤。 使用 union 關鍵字宣告聯合類型:
public union Pet(Cat, Dog, Bird);
此宣告產生 Pet 包含三種情況類型的聯集: Cat、、 Dog和 Bird。 你可以將任何案件類型的值指派給變 Pet 數。 編譯器確保 switch 表達式涵蓋所有案例類型。
C# 語言參考資料記錄了 C# 語言最新版本。 同時也包含即將推出語言版本公開預覽功能的初步文件。
文件中標示了語言最近三個版本或目前公開預覽版中首次引入的任何功能。
小提示
欲查詢某功能何時首次在 C# 中引入,請參閱 C# 語言版本歷史的條目。
當值必須是固定型別集合中的一個,且你希望編譯器強制處理所有可能性時,宣告聯集。 常見情況包括:
-
結果或錯誤回傳:方法回傳成功值或錯誤值,呼叫者必須同時處理兩者。 像
union Result(Success, Error)這樣的聯合會明確呈現結果的集合。 -
訊息或指令派遣:系統處理一組封閉的訊息類型。 聯合體確保新訊息類型會在尚未處理的每個
switch訊息類型產生編譯時警告。 - 取代標記介面或抽象基底類別:如果你只用介面或抽象類別來群組型態以進行模式匹配,union 可以讓你在不需要繼承或共享成員的情況下,提供徹底性檢查。
聯集在重要方面與其他類型宣告不同:
- 與 或
struct不同class,聯合體不會定義新的資料成員。 相反地,它將現有類型組合成一組封閉的替代方案。 - 與 不同
interface,聯集是封閉的——你在宣告中定義完整的案例類型清單,編譯器會用該清單進行窮盡性檢查。 - 與 不同
record,聯合不會增加等式、克隆或解構行為。 一個工會關注的是「是哪個案件?」而不是「它有哪些領域?」
這很重要
在 .NET 11 預覽版 2 中,執行時不包含 UnionAttribute 和 IUnion 介面。 要使用聯合類型,必須自行申報。 欲查看所需申報,請參閱 聯合實施。
聯盟宣言
聯合聲明指定名稱及案件類型清單:
public union Pet(Cat, Dog, Bird);
案例類型 可以是任何能轉換成 object的類型,包括類別、結構體、介面、型別參數、可空型態及其他聯集。 以下範例展示了不同的案例類型可能性:
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);
當一個案例類型是值型別(如 int),該值在合併 Value 的屬性中被封框。 工會會將內容作為單一 object? 參考資料儲存。
工會聲明可以包含一個包含額外成員的機構,就像結構組織一樣,但會受到一些限制。 聯合聲明不能包含實例欄位、自動屬性或類欄位事件。 你也不能用單一參數宣告公開建構子,因為編譯器會以聯合創建成員的形式產生這些建構子:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
工會轉型
每個案件類型之間存在隱含 的聯集轉換 。 你不需要明確呼叫建構子:
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 }
}
聯集轉換是透過呼叫相應產生的建構子來運作。 若存在相同類型的使用者定義隱含轉換運算子,該使用者定義運算子優先於聯集轉換。 關於轉換優先權的詳細資訊,請參閱 語言規範。
當 是聯集型態時,也可T將 Union 轉換成可空的 union 結構T?體()。
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",
};
}
聯合配對
當你對聯合體類型進行模式匹配時,模式會 Value 套用在聯合體的屬性上,而不是聯合體本身的值。 這種「展開」行為意味著聯集對模式匹配是透明的:
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
}
有兩種模式是此規則的例外: var 模式與棄牌 _ 模式適用於合併值本身,而非其 Value 性質。 當回傳 () 時,請用var來擷取聯集值GetPet():Nullable<Pet>Pet?
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
在邏輯模式中,每個分支分別遵循展開規則。 以下模式測試 不是Pet?空,也Value不是空:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}
備註
由於 模式適用於 Value,而 的 pet is Pet 模式通常不匹配,因為 Pet 是與聯集 內容 測試,而非聯集本身。
空匹配
對於結構聯集,該 null 模式會檢查是否 Value 為空:
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
}
對於基於類別的聯集, null 當聯集參考本身為空或其 Value 性質為空時,成功:
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 */ }
對於可空聯集的結構型別(Pet?), null 當可空封裝器沒有值或底層聯集 Value 為空值時,該類型會成功。
聯合的窮盡性
當一個 switch 表達式處理聯合體的所有案例類型時,即為窮盡式。 編譯器只有在未處理案件類型時才會發出警告。 你不需要包含棄置圖案(_)或 var 任何類型所需的圖案:
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
}
如果聯合元 Value 屬性的 null 狀態是「可能是 null」,你也必須處理 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
}
可空性
編譯器透過以下規則追蹤聯合屬性 Value 的空狀態:
- 當你從案例類型(透過建構子或合併轉換)建立聯合值時,
Value會得到該輸入值的空狀態。 - 當非盒定存取模式
HasValue或TryGetValue(...)成員查詢聯合集內容時,的Value空狀態在分支true上變成「非空」。
海關聯盟類型
編譯器會將宣 union 告轉換成宣 struct 告。 結構體標示屬性,[System.Runtime.CompilerServices.Union]實作介面。IUnion 它包含一個公開建構子,以及每個案例類型的隱含轉換,還有一個 Value 屬性。 這種生成的形式很有主見。 它總是結構體,總是用值型箱子,並且總是以 object?。
當你需要不同的行為——例如基於類別的聯集、自訂儲存策略、互通支援,或是想調整現有型別——你可以手動建立聯集型別。
任何帶有屬性的[Union]類別或結構體,只要遵循基本聯集模式,則稱為聯合型別。 基本的合併模式要求:
- 屬性
[Union]在類型上。 - 一個或多個公共建構子,每個建構子有一個單一的參數或
in參數。 每個建構子的參數類型定義了一種 案例類型。 - 一個類型(或
object)的公共Value財產object?,帶有get一個附件。
所有工會成員必須公開。 編譯器利用這些成員來實作聯集轉換、模式匹配及徹底性檢查。 你也可以實作 非盒裝存取模式 ,或建立 基於類別的聯集型態。
編譯器假設自訂聯集類型符合以下行為規則:
-
正確性:
Value總是返回null或某一箱子類型的值,絕不會是其他類型的值。 對於結構聯集,產生default的 aValue。null -
穩定性:如果你從某個案例類型建立一個聯合值,該
Value值與該案件類型相符(或若輸入是null)。null - 創建等價性:若一個值隱含可轉換為兩種不同案例類型,兩個創建成員產生相同的可觀察行為。
-
存取模式一致性:
HasValue若 和TryGetValue成員存在,其行為等同於直接檢查Value。
以下範例展示了一種自訂聯合類型:
[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
}
非包箱存取模式
自訂聯集類型可選擇性實作 非盒裝存取模式 ,以強型別存取值型案件,且不會在模式匹配時進行盒裝。 此圖案要求:
- 一個 類型的
bool屬性,當Value不回傳true時 則不是null。HasValue - 每個案件類型都有
TryGetValue一套方法,透過參數out回傳bool並交付該值。
[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
}
編譯器在實作模式匹配時偏好 TryGetValue 於該 Value 性質,以避免盒裝值型別。
基於類別的聯合類型
類別也可以是聯集類型。 這種聯集在需要參考語意或繼承時非常有用:
[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",
};
}
對於類別聯集,該 null 模式同時匹配一個空參考與一個空 Value參考。
工會實施
以下屬性與介面在編譯時及執行時支援聯合型別:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
編譯器產生的聯合宣告實作 IUnion。 你可以在執行時透過使用 IUnion: 來檢查任何聯合值:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
當你宣告一個 union 型別時,編譯器會產生一個結構 IUnion體來實作 。 例如, Pet 宣告public union Pet(Cat, Dog, Bird);()等價於:
[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; }
}
這很重要
在 .NET 11 預覽版 2 中,這些類型並未包含在執行時中。 要使用聯合型別,必須在專案中宣告它們。 它們將會納入未來的 .NET 預覽版中。
C# 語言規格
欲了解更多資訊,請參閱 Union 功能規範。