×

C# 终于支持 union types 了

独孤求败 独孤求败 发表于2026-05-26 10:19:38 浏览16 评论0

抢沙发发表评论

C# 15 中的联合 union

Intro

union 联合类型在 C# 中的需求一直很高,现在终于要来了。从 .NET 11 Preview 2 开始,C# 15 引入了 union 关键字。union 关键字声明一个值恰好是固定类型集合中的一种,并且具有编译器强制执行的穷尽模式匹配。C# 的联合旨在提供原生的 C# 使用体验:它们是组合现有类型的类型联合,与模式匹配集成,并与语言的其余部分无缝协作。

What

union 可以是几个不同类型的组合,换句话说,一个值只能是几种指定类型中的其中一种。

--- 有穷尽的类型枚举

联合体是一组相互关联的特性,它们共同为 C# 提供对联合体类型的支持:

  • • 联合体类型:带有 [Union] 特性的结构体和类会被识别为联合体类型,并支持联合体行为。
  • • 案例类型:联合类型具有一组案例类型,表示其内容值可以具有的类型。
  • • 联合体行为:联合体类型支持以下联合体行为:
    • • 联合体转换:每个类型都存在到联合体的隐式转换。
    • • 联合体匹配:对联合体值进行模式匹配时,会隐式地“解包”其内容,并将模式应用于底层值 Value
    • • 联合体穷尽性:当所有类型都匹配完毕时,对联合体值执行 switch 表达式是穷尽的,无需回退类型。
    • • 联合体可空性:可空性分析增强了对联合体内容空状态的跟踪。
  • • 联合体模式:所有联合体类型都遵循一个基本的联合体模式,但还有一些可选模式用于特定场景。
  • • 联合体声明:可以使用简写语法直接声明联合体类型。该实现方式是“预设的”——结构体声明遵循基本的联合模式,并将内容存储为单个引用字段。

在 .NET Preview 4 之前要使用的话还需要声明以下 polyfill 代码,在 .NET 11 Preview 4 之后就不需要自己声明了,已经包含在框架类库中了 https://github.com/dotnet/runtime/pull/127001

namespace System.Runtime.CompilerServices;

[AttributeUsage(Class | Struct, AllowMultiple = false, Inherited = false)]
public
 sealed class UnionAttribute : Attribute;

public
 interface IUnion
{
    object
? Value { get; }
}

Samples

Get Started

举个例子:

public record class Cat(string Name);
public record class Dog(string Name);
public union Pet(Cat, Dog);

这里定义了 Cat 和 Dog 两个类型,然后定义了一个 union 这个 union 由 Cat 和 Dog 组成,这个 Pet union 就可以是 Dog 也可以是 Cat 不可以是其他的类型

使用示例如下:

Pet pet = new Cat("A");
Console.WriteLine(pet switch
{
    Cat c => $"Cat: {c.Name}",
    Dog d => $"Dog: {d.Name}"
});

这里我们不需要指定 _ fallback 匹配,因为 Pet 只会出现 Cat/Dog 这两种类型,编译器也不会给出警告

如果未来 Pet 新增了其他类型,编译器就会给出警告

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Xxx' is not covered.

union 的声明会自动创建隐式转换,像前面的 Pet pet = new Cat("A"); 一样,编译器可以自动完成从具体类型到联合类型的隐式转换

联合还比较适合用于 Result 模式,示例如下:

public union GetUserResult(User, NotFoundError, ValidationError);

return
 result switch
{
    User u => Ok(u),
    NotFoundError e => NotFound(e.Message),
    ValidationError e => BadRequest(e.Message),
};

反编译看一下代码:

图片
`Pet decompilation`


可以看到 union 默认是生成了一个 struct 并且添加了 Union Attribute 并实现了 IUnion 接口,通过 Value 返回其中的对象,这里可以看到如果是值类型的话就会发生装箱,这对于一些对于热路径性能敏感的代码处使用默认的实现可能会存在性能问题,我们也可以有自己的实现来避免发生装箱

public union IntegerOrString(int, string);

 IntegerOrString union1 = 42;
 IntegerOrString union2 = "Hello";
 Console.WriteLine($"Union1: {union1.Value}");
 Console.WriteLine($"Union2: {union2.Value}");


图片
`IntegerOrString`

Avoid Boxing

在默认的实现中,对于值类型来说会出现装箱的问题,如上面的示例所示,对比 union 也支持了另外一个模式来避免装箱

// Non-boxing access members
public
 bool HasValue { get { ... } }
public bool TryGetValue(out Dog value) { ... }
  • • 如果 Union 的 Value 不是 nullHasValue 应该返回 true
  • • TryGetValue 仅在联合的值是给定案例类型的非空值时返回 true,并且如果是这样,则将该值传递到方法的 out 参数中。

避免装箱的一个示例如下:

[Union]
public
 readonly struct IntegerOrStringCustomized
{
    private
 readonly string? _value;
    private
 readonly int? _num = null;
    public IntegerOrStringCustomized(int num)
    {
        _num = num;
    }
    public IntegerOrStringCustomized(string value)
    {
        _value = value;
    }

    public
 object? Value => _num is null ? _value : $"{_num}";
    public
 bool HasValue => _value is not null && _num is not null;

    public bool TryGetValue(out int? value)
    {
        if
 (_num is int intNum)
        {
            value
 = intNum;
            return
 true;
        }

        value
 = null;
        return
 false;
    }

    public bool TryGetValue(out string? value)
    {
        if
 (_value is string stringValue)
        {
            value
 = stringValue;
            return
 true;
        }

        value
 = null;
        return
 false;
    }
}

使用示例如下:

IntegerOrStringCustomized union3 = 42;
if
 (union3.TryGetValue(out int? num))
{
    Console.WriteLine($"Union3 integer value is {num}");
}

union3 = "Hello";
if
 (union3.TryGetValue(out string? str))
{
    Console.WriteLine($"Union3 string value is {str}");
}

这里的 Union Attribute 是必须的,否则不会被当作 union 不能进行隐式转换,会得到下面的错误

// CS0029: Cannot implicitly convert type 'int' to 'CSharp15Samples.IntegerOrStringCustomized'

另外如果要支持 switch 模式匹配的话必须要有一个公开的 object? Value { get; } 属性,不然编译器会报错

// Missing compiler required member 'IntegerOrStringCustomized.Value'

使用如下:

var stringValue = union3 switch
{
    int
 i => i.ToString(),
    string
 s => s
};
Console.WriteLine(stringValue);

反编译看一下实现会是什么样的

图片

More

目前 union 还在不断的完善,包括 union 本身的优化如 IUnionMembers 等以及框架的支持比如 JSON 序列化和 ASP.NET Core 的支持,期待越来越完善好用。


群贤毕至

访客