最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#?15?類型系統(tǒng)改進(jìn)Union?Types詳解

 更新時(shí)間:2026年04月23日 14:07:58   投稿:mrr  
本文介紹了C#15中引入的Union類型及其用法,Union類型允許將一個(gè)值限定為一組封閉類型中的某一種,并在針對Union值的switch表達(dá)式中獲得窮盡性檢查,文章通過示例展示了Union聲明、轉(zhuǎn)換、匹配和窮盡性檢查等特性,感興趣的朋友跟隨小編一起看看吧

前言

Union 類型(聯(lián)合類型)一直是 C# 社區(qū)呼聲最高的特性之一。從最初的 discriminated unions 提案到今天,這個(gè)特性經(jīng)歷了多年的設(shè)計(jì)和討論,終于在 C# 15 中正式落地。

Union 類型允許我們將一個(gè)值限定為一組封閉類型中的某一種,并且在針對 Union 值的 switch 表達(dá)式中獲得窮盡性檢查。編譯器能幫你確認(rèn)是否處理了所有 case 類型,很多時(shí)候就不再需要那個(gè)煩人的 _ 兜底分支。

本文將介紹 C# 15 中 Union 類型的設(shè)計(jì)和用法。

從一個(gè)實(shí)際問題出發(fā)

假設(shè)我們要實(shí)現(xiàn)一個(gè)函數(shù),它可能返回一個(gè)正常的結(jié)果,也可能返回一個(gè)錯誤。以前常見的做法是定義一個(gè)包裝類:

public class Result<T>
{
    public T? Data { get; set; }
    public Exception? Error { get; set; }
    public bool IsSuccess => Error is null;
}

這種寫法有一個(gè)很明顯的問題:DataError 在類型上同時(shí)存在,編譯器沒法保證「成功時(shí)一定有 Data」或「失敗時(shí)一定有 Error」。正確性全靠人為約定,而不是類型系統(tǒng)來保障。

有了 Union 類型,這個(gè)問題就迎刃而解了。

Union 聲明

C# 15 引入了全新的 union 關(guān)鍵字,可以用非常簡潔的語法聲明一個(gè)聯(lián)合類型:

public union Pet(Cat, Dog, Bird);

就是這么簡單!這一行聲明了一個(gè)名為 Pet 的聯(lián)合類型,它的值可以是 Cat、DogBird 中的任何一種。

Union 聲明會被編譯器展開為一個(gè)結(jié)構(gòu)體,內(nèi)部用單個(gè) object 引用來存儲值:

// 編譯器生成的等價(jià)代碼
[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; }
}

也就是說,Union 聲明就是一種簡潔的結(jié)構(gòu)體聲明方式,編譯器幫你生成了所有樣板代碼。

再來看一個(gè)更實(shí)用的例子,利用 Union 和已有類型組合來實(shí)現(xiàn) Option<T>

public record class None();
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);

還可以在 Union 中添加自定義方法:

public union OneOrMore<T>(T, IEnumerable<T>)
{
    public IEnumerable<T> AsEnumerable() => Value switch
    {
        IEnumerable<T> list => list,
        T value => [value],
    };
}

這也很方便!

另外,case 類型并不只限于這里展示的具體類。按照提案,它們還可以是接口、類型參數(shù)、可空類型,甚至其他 Union,而且 case 之間允許重疊。

不過,Union 聲明本身是一種有意“收緊”的聲明形式。你可以添加方法之類的成員,但不能聲明實(shí)例字段、自動屬性或類字段事件;你也不能自己聲明 public 的單參數(shù)構(gòu)造函數(shù),而你顯式添加的構(gòu)造函數(shù)必須通過 this(...) 委托到編譯器生成的 case 構(gòu)造函數(shù)之一。

Union 轉(zhuǎn)換

Union 類型支持從每個(gè) case 類型到聯(lián)合類型的隱式轉(zhuǎn)換:

Cat cat = new Cat("小花");
Pet pet = cat; // 隱式 union 轉(zhuǎn)換,不需要顯式構(gòu)造

編譯器會將其轉(zhuǎn)換為對構(gòu)造函數(shù)的調(diào)用:

// 編譯器實(shí)際生成的代碼
Pet pet = new Pet(cat);

這意味著你不需要手動去包裝值,直接賦值就行了。如果你之前有自定義的隱式轉(zhuǎn)換運(yùn)算符,那它的優(yōu)先級會高于 union 轉(zhuǎn)換,所以現(xiàn)有代碼不會受到影響。

這里還有一個(gè)容易忽略的點(diǎn):Union 轉(zhuǎn)換只有隱式形式。即使某個(gè) case 類型存在顯式轉(zhuǎn)換,也不代表因此就自動擁有到整個(gè) Union 類型的顯式轉(zhuǎn)換。

Union 匹配

Union 類型真正的威力在于和模式匹配的配合。

當(dāng)你對一個(gè) Union 值進(jìn)行模式匹配時(shí),編譯器會自動拆包內(nèi)部的值:

Pet pet = GetPet();
if (pet is Dog dog)
{
    // dog 已經(jīng)是 Dog 類型,直接使用
    dog.Bark();
}
// switch 表達(dá)式
string description = pet switch
{
    Dog dog => $"這是一只狗:{dog.Name}",
    Cat cat => $"這是一只貓:{cat.Name}",
    Bird bird => $"這是一只鳥:{bird.Name}",
};

注意最后一個(gè)分支后面沒有 _ 兜底!因?yàn)檫@是一個(gè)針對 Union 值的 switch 表達(dá)式,而編譯器也知道 Pet 的 case 類型只有 Dog、CatBird,所以它可以把這個(gè)表達(dá)式視為窮盡的。

這確實(shí)是一個(gè)非常實(shí)用的功能,不僅簡化了代碼,還在編譯期幫你保證了安全性。假如以后你給 Pet 增加了一個(gè)新的 case 類型 Fish,那么所有沒有處理 Fishswitch 表達(dá)式都會產(chǎn)生編譯警告,避免了遺漏。

對于無條件的 var_ 模式,匹配的是 Union 值本身而不是內(nèi)部值:

if (pet is var p) { ... } // p 是 Pet 類型,不是 object

這是有意為之。var 通常只是給當(dāng)前值起個(gè)名字,保留 Union 類型比解出一個(gè) object? 更實(shí)用。

這也意味著,pet is Pet p 并不等同于 pet is var p。在 Pet p 這樣的類型模式里,Pet 會作用在拆包后的內(nèi)部值上,而不是外層的 Union 值本身,所以這個(gè)模式通常不會成功。

null 模式還有一個(gè)值得專門提醒的細(xì)節(jié)。對于基于 class 的 Union,result is null 在兩種情況下都會成功:Union 對象本身是 null,或者它內(nèi)部的 Valuenull。對于 U? 這種“nullable 包裹 struct Union”的情況也類似:如果外層 nullable 沒有值,或者內(nèi)部 Union 的 Valuenull,那么 u is null 都會成功。相對地,其他 Union 匹配模式只有在外層值本身存在時(shí)才會成功。

Union 窮盡性

剛才提到了窮盡性檢查,這是 Union 類型最重要的能力之一。

union Result(int, string, Exception);
string Describe(Result r) => r switch
{
    int n => $"數(shù)字:{n}",
    string s => $"字符串:{s}",
    Exception e => $"錯誤:{e.Message}",
    // 編譯器認(rèn)為已窮盡,無需 _ 分支
};

但如果 Union 的值可能為 null(例如某個(gè) case 類型是可空的),編譯器會要求你處理 null 的情況:

Pet pet = GetNullableDog(); // pet.Value 可能是 null
var result = pet switch
{
    Dog dog => "汪",
    Cat cat => "喵",
    Bird bird => "啾",
    // 警告:未處理 null
};

手動實(shí)現(xiàn) Union 模式

Union 聲明雖然方便,但并不是獲得 Union 行為的唯一方式。你完全可以在已有類型上手動實(shí)現(xiàn) Union 模式,只需滿足以下條件:

  1. 類型標(biāo)記 [Union] 屬性
  2. 提供對應(yīng)每個(gè) case 類型的單參數(shù)構(gòu)造函數(shù)
  3. 提供一個(gè) object? 類型的 Value 屬性
[Union]
public struct IntOrString
{
    private readonly object _value;
    public IntOrString(int value) => _value = value;
    public IntOrString(string value) => _value = value;
    public object? Value => _value;
}

這在需要適配已有類型,或者需要自定義存儲策略時(shí)非常有用。

Union 成員提供者(IUnionMembers)

默認(rèn)情況下,編譯器通過 Union 類型自身的構(gòu)造函數(shù)來識別 case 類型。但有些場景下你可能不想暴露公開構(gòu)造函數(shù),或者想用工廠方法來創(chuàng)建 Union 值。這時(shí)可以在 Union 類型內(nèi)部聲明一個(gè)名為 IUnionMembers 的接口,讓它充當(dāng)成員提供者。

[Union]
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;
}

當(dāng) Union 類型內(nèi)部包含 IUnionMembers 接口聲明時(shí),編譯器就不再從 Union 類型本身查找構(gòu)造函數(shù)了,而是從這個(gè)接口上的 Create 工廠方法來確定 case 類型。Value 屬性也改為在接口上聲明。

這種模式有幾個(gè)好處:

  • 可以對外隱藏構(gòu)造函數(shù),只通過工廠方法創(chuàng)建實(shí)例
  • 適合 class 類型的 Union(Union 聲明默認(rèn)生成的是 struct
  • 可以靈活控制內(nèi)部存儲和初始化邏輯

使用時(shí),隱式轉(zhuǎn)換會自動走工廠方法:

Result<string> result = "Hello";
// 等價(jià)于
Result<string> result = Result<string>.IUnionMembers.Create("Hello");

Non-boxing 訪問模式

默認(rèn)的 Union 模式通過 object? 類型的 Value 屬性訪問內(nèi)部值,這意味著值類型會產(chǎn)生裝箱。如果你對性能有更高要求,可以額外實(shí)現(xiàn) HasValueTryGetValue 方法,讓編譯器在模式匹配時(shí)使用強(qiáng)類型的訪問路徑:

[Union]
public 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 ? (object)(_value == 1) : _value;
    // Non-boxing 訪問模式
    public bool HasValue => true;
    public bool TryGetValue(out int value)
    {
        value = _value;
        return !_isBool;
    }
    public bool TryGetValue(out bool value)
    {
        value = _isBool && _value == 1;
        return _isBool;
    }
}

這樣編譯器在進(jìn)行模式匹配時(shí),就不需要通過 Value 屬性來裝箱獲取值了,而是直接調(diào)用對應(yīng)的 TryGetValue,從而避免了裝箱開銷。

Result 模式的例子

讓我們回到文章開頭的問題,用 Union 來實(shí)現(xiàn)一個(gè)類型安全的 Result<T>

public union Result<T>(T, Exception);

一行搞定。用起來是這樣的:

Result<int> Divide(int a, int b)
{
    if (b == 0) return new DivideByZeroException();
    return a / b;
}
var result = Divide(10, 3);
var message = result switch
{
    int value => $"結(jié)果是 {value}",
    Exception ex => $"出錯了:{ex.Message}",
};

不需要額外的包裝類,不需要 IsSuccess 屬性,類型系統(tǒng)保證了每種情況都被處理。比以前的做法優(yōu)雅得多。

Union 與類型層次結(jié)構(gòu)

值得一提的是,C# 的 Union 類型是類型的聯(lián)合而不是帶標(biāo)簽的聯(lián)合。如果你需要更接近傳統(tǒng) discriminated unions 的效果(即每個(gè)分支有獨(dú)立的名稱和數(shù)據(jù)),可以用 record 作為 case 類型來組合:

public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
public record class Triangle(double Base, double Height);
public union Shape(Circle, Rectangle, Triangle);
double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r => r.Width * r.Height,
    Triangle t => 0.5 * t.Base * t.Height,
};

如果不需要命名的分支,用現(xiàn)有的類型直接組合就好。兩種方式各有適用場景,C# 在設(shè)計(jì)上給了充分的靈活性。

另外,如果你需要更加嚴(yán)格的封閉類型層次結(jié)構(gòu),還可以關(guān)注即將推出的 closed hierarchies 特性,它和 Union 類型是互補(bǔ)的關(guān)系。

為什么不用類型擦除?

看到這里你可能會想:Union 值在運(yùn)行時(shí)不就是一個(gè) object 引用嗎?那為什么不直接用 object 加上編譯期的元數(shù)據(jù)信息來表示 Union 類型呢?也就是說,讓 union Pet(Cat, Dog) 直接擦除成 object,編譯器靠 attribute 之類的元數(shù)據(jù)來記住這里其實(shí)是 Pet,跟 dynamic、元組名和可空注解的處理方式一樣。

這個(gè)思路在 C# 語言設(shè)計(jì)工作組中被認(rèn)真討論過,而且在類型聯(lián)合提案中也確實(shí)有 Ad Hoc Union 這一類設(shè)計(jì)是基于擦除的。但最終它沒有成為 C# 15 的實(shí)現(xiàn)方案,原因有幾個(gè):

泛型場景下會破壞類型安全。 考慮這樣一段代碼:

public class MyCollection<T>
{
    public bool TryAdd(object o)
    {
        if (o is T t)
        {
            // 添加 t
            return true;
        }
        return false;
    }
}

如果用 MyCollection<(int or string)> 實(shí)例化,而 (int or string) 被擦除成了 object,那么 o is T 就變成了 o is object,永遠(yuǎn)成功。任何類型的值都能繞過檢查被塞進(jìn)集合,類型安全就徹底崩了。

不擦除也有問題。 如果換成包裝類型 ValueUnion<T1, T2> 來避免擦除,那 (string or bool)(bool or string) 在運(yùn)行時(shí)就是不同的類型。這對 ad hoc union 來說是無法接受的,因?yàn)橛脩糇匀粫J(rèn)為這兩個(gè)同一組類型的聯(lián)合應(yīng)該可以互換使用。工作組曾調(diào)研過運(yùn)行時(shí)層面的解決方案,但結(jié)論是不完美且代價(jià)巨大。

包裝類型方案在實(shí)用性上勝出。 語言設(shè)計(jì)工作組整理過一份 Trade Off Matrix,對比了三種可行路線:類層次結(jié)構(gòu)、object 引用(擦除)和包裝類型。最終選擇的包裝類型方案(即現(xiàn)在的 Nominal Type Unions)在向后兼容性、非 ABI 破壞性、可定制實(shí)現(xiàn)以及交付周期等維度上都有明顯優(yōu)勢。擦除方案雖然在匿名語法和動態(tài)模式匹配方面更出色,但需要較大的運(yùn)行時(shí)改造才能安全工作,短期內(nèi)無法落地。

所以最終的設(shè)計(jì)是:Union 聲明生成一個(gè)結(jié)構(gòu)體包裝,內(nèi)部用 object? 引用存值。你可以把它理解為一個(gè)編譯器幫你維護(hù)的包裝類型,但它不是單純的元數(shù)據(jù)注解,運(yùn)行時(shí)確實(shí)存在這個(gè)結(jié)構(gòu)體,Value 屬性也是真實(shí)可訪問的。這讓 Union 在反射、序列化、跨程序集調(diào)用等場景下都能正確工作,而不只是一個(gè)編譯器的錯覺。

結(jié)語

Union 類型的加入,是 C# 類型系統(tǒng)一次質(zhì)的飛躍。它解決了長期以來用 C# 表達(dá)多選一類型時(shí)的尷尬:不再需要靠約定、靠運(yùn)行時(shí)檢查,而是讓編譯器從類型層面幫你把關(guān)。

簡潔的 union 聲明語法讓大多數(shù)場景幾行代碼就搞定,而靈活的 Union 模式又允許在需要時(shí)完全自定義底層實(shí)現(xiàn)。這種簡單場景簡單做,復(fù)雜場景有出路的設(shè)計(jì)理念,非常符合 C# 一貫的風(fēng)格。

期待 C# 15 和 .NET 11 的正式發(fā)布~

到此這篇關(guān)于C# 15 類型系統(tǒng)改進(jìn):Union Types的文章就介紹到這了,更多相關(guān)C# 15 類型系統(tǒng)改進(jìn):Union Types內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#實(shí)現(xiàn)繪制隨機(jī)噪點(diǎn)和直線

    C#實(shí)現(xiàn)繪制隨機(jī)噪點(diǎn)和直線

    這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)繪制隨機(jī)噪點(diǎn)和直線,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-01-01
  • C#實(shí)現(xiàn)HTTP訪問類HttpHelper的示例詳解

    C#實(shí)現(xiàn)HTTP訪問類HttpHelper的示例詳解

    在項(xiàng)目開發(fā)過程中,我們經(jīng)常會訪問第三方接口,如我們需要接入的第三方接口是Web API,這時(shí)候我們就需要使用HttpHelper調(diào)用遠(yuǎn)程接口了。本文為大家介紹了C#實(shí)現(xiàn)HTTP訪問類HttpHelper的示例代碼,需要的可以參考一下
    2022-09-09
  • 使用C#定時(shí)刷新已經(jīng)登錄過的網(wǎng)頁

    使用C#定時(shí)刷新已經(jīng)登錄過的網(wǎng)頁

    日常工作中,登錄內(nèi)部系統(tǒng)往往需要輸入用戶名、密碼、短信驗(yàn)證碼,但是如果30分鐘不操作,會自動退出,為避免頻繁登錄,可以使用c#寫一個(gè)自動刷新指定網(wǎng)址頁面的程序,所以本文給大家介紹了如何使用C#定時(shí)刷新已經(jīng)登錄過的網(wǎng)頁,需要的朋友可以參考下
    2025-12-12
  • C#實(shí)現(xiàn)自定義雙擊事件

    C#實(shí)現(xiàn)自定義雙擊事件

    這篇文章主要介紹了C#實(shí)現(xiàn)自定義雙擊事件,需要的朋友可以參考下
    2014-08-08
  • 利用WPF實(shí)現(xiàn)系統(tǒng)資源監(jiān)控的完整代碼

    利用WPF實(shí)現(xiàn)系統(tǒng)資源監(jiān)控的完整代碼

    在現(xiàn)代軟件開發(fā)中,系統(tǒng)資源監(jiān)控是系統(tǒng)管理、性能分析和故障診斷的重要工具,WPF是構(gòu)建現(xiàn)代化、美觀實(shí)用的系統(tǒng)監(jiān)控應(yīng)用的理想選擇,本文將詳細(xì)介紹如何使用WPF創(chuàng)建一個(gè)功能全面的系統(tǒng)資源監(jiān)控儀表盤,需要的朋友可以參考下
    2026-01-01
  • C#連接SQLite數(shù)據(jù)庫并實(shí)現(xiàn)基本操作

    C#連接SQLite數(shù)據(jù)庫并實(shí)現(xiàn)基本操作

    本文介紹了SQLite,一個(gè)輕量級的跨平臺數(shù)據(jù)庫管理系統(tǒng),以及如何在C#中使用System.Data.SQLite庫進(jìn)行操作,包括創(chuàng)建、修改和查詢數(shù)據(jù)庫,以及使用SQLiteHelper類簡化SQL使用,此外,還提到了DB文件查看工具SQLiteSpy的應(yīng)用,需要的朋友可以參考下
    2024-12-12
  • HttpWebRequest出錯.Section=ResponseHeader Detail=CR

    HttpWebRequest出錯.Section=ResponseHeader Detail=CR

    HttpWebRequest出錯.Section=ResponseHeader Detail=CR...
    2007-03-03
  • C#循環(huán)與循環(huán)控制的表達(dá)式樹實(shí)現(xiàn)

    C#循環(huán)與循環(huán)控制的表達(dá)式樹實(shí)現(xiàn)

    這篇文章介紹了C#循環(huán)與循環(huán)控制的表達(dá)式樹實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • C#實(shí)現(xiàn)將類的內(nèi)容寫成JSON格式字符串的方法

    C#實(shí)現(xiàn)將類的內(nèi)容寫成JSON格式字符串的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)將類的內(nèi)容寫成JSON格式字符串的方法,涉及C#針對json格式數(shù)據(jù)轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-08-08
  • C#WinForm實(shí)現(xiàn)多語言切換的示例

    C#WinForm實(shí)現(xiàn)多語言切換的示例

    本文主要介紹了C#WinForm實(shí)現(xiàn)多語言切換的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01

最新評論

江孜县| 霸州市| 聂拉木县| 静安区| 永登县| 大余县| 达州市| 湛江市| 祁门县| 西安市| 通榆县| 鄂伦春自治旗| 乡城县| 武定县| 时尚| 垦利县| 腾冲县| 宁南县| 南宁市| 哈巴河县| 晋城| 海门市| 荥经县| 公主岭市| 台中县| 保定市| 缙云县| 龙海市| 章丘市| 麟游县| 瓦房店市| 蒙山县| 齐河县| 临泽县| 吉木萨尔县| 额敏县| 武定县| 宝坻区| 保靖县| 新密市| 基隆市|