.NET中联合类型的实现(OneOf框架核心机理讲解)

导言

大家使用Python或者Typescript的时候,可能对于它们支持的联合类型比较羡慕,他们写起来象这样:

typescript 复制代码
let value: string | number;
value = "hello";
value = 123;

是不是很方便? 但是在C#中实现联合类型有更优雅的实现,并且提供强类型检测!

🚀C#中最小联合类型实现

  • 新建一个dotnet console项目,使用命令: dotnet new console -n Test --framework net9.0
  • 新建一个类OneOf.cs,在里面实现一个最小的联合类型:
csharp 复制代码
public struct OneOf<T0, T1>
{
    private readonly int _index;
    private readonly Object _Value;

    public OneOf(T0 value)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));
        _index = 0;
        _Value = value;
    }
    public OneOf(T1 value)
    {
        _index = 1;
        _Value = value!;
    }

    public static implicit operator OneOf<T0, T1>(T0 value) => new OneOf<T0, T1>(value);
    public static implicit operator OneOf<T0, T1>(T1 value) => new OneOf<T0, T1>(value);

    public bool IsT0 => _index == 0;
    public bool IsT1 => _index == 1;

    public T0 AsT0 => IsT0 ? (T0)_Value! : throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}");
    public T1 AsT1 => IsT1 ? (T1)_Value! : throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}");

    public override string ToString()
    {
        return _Value?.ToString() ?? "";
    }
}

👉 这里的核心思想就是:

  • 一个 object 存储实际值
  • 一个 int 记录当前类型索引
  • 提供隐式转换 + 访问方法

这就是最小化的"联合类型"实现。

  • 在Program.cs中测试一下:
csharp 复制代码
using System.Globalization;
//我们即可以给他赋值为字符型,也可以数值型
OneOf<string, int> test = "hello";
test = 5;

var result1 = Myfunction(true);
Console.WriteLine(result1);

//可以返回字符型,也可以返回数字型
OneOf<string, int> Myfunction(bool flag)
{
    if (flag)
    {
        return "hello";
    }
    else
    {
        return 5;
    }
}

👉 这样我们就可以把实现的OneOf<T0,T1>联合类型在赋值中、返回值、方法参数中等等使用! 😊怎么样?是不是使用起来很方便?

🎖️"联合类型"进阶

让我们再实现两个辅助方法:Switch与Match OneOf.cs中的OneOf类看起来是这样的:

csharp 复制代码
public struct OneOf<T0, T1>
{
    private readonly int _index;
    private readonly Object _Value;

    public OneOf(T0 value)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));
        _index = 0;
        _Value = value;
    }
    public OneOf(T1 value)
    {
        _index = 1;
        _Value = value!;
    }

    public static implicit operator OneOf<T0, T1>(T0 value) => new OneOf<T0, T1>(value);
    public static implicit operator OneOf<T0, T1>(T1 value) => new OneOf<T0, T1>(value);

    public bool IsT0 => _index == 0;
    public bool IsT1 => _index == 1;

    public T0 AsT0 => IsT0 ? (T0)_Value! : throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}");
    public T1 AsT1 => IsT1 ? (T1)_Value! : throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}");

    public override string ToString()
    {
        return _Value?.ToString() ?? "";
    }
    /// <summary>
    /// 对于没有返回情况的处理
    /// </summary>
    /// <param name="f0"></param>
    /// <param name="f1"></param>
    public void Switch(Action<T0> f0, Action<T1> f1)
    {
        if (_index == 0 && f0 != null)
        {
            f0((T0)_Value!);
            return;
        }
        if (_index == 1 && f1 != null)
        {
            f1((T1)_Value!);
            return;
        }
    }

    /// <summary>
    /// 对于有返回情况的处理
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="f0"></param>
    /// <param name="f1"></param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    public TResult Match<TResult>(Func<T0, TResult> f0, Func<T1, TResult> f1)
    {
        if (_index == 0 && f0 != null)
        {
            return f0((T0)_Value!);
        }
        if (_index == 1 && f1 != null)
        {
            return f1((T1)_Value!);
        }
        throw new InvalidOperationException();
    }
}

这两个辅助方法非常强大,看起来我们使用是这样的:

ini 复制代码
using System.Globalization;

OneOf<string, int> test = "hello";

test.Switch(
  str => Console.WriteLine(str),
  num => Console.WriteLine(num.ToString())
);

var result = Myfunction(false);

var result1 = result.Match(
    str => str.Length,
    num => num * 2
);
Console.WriteLine(result1);

OneOf<string, int> Myfunction(bool flag)
{
    if (flag)
    {
        return "hello";
    }
    else
    {
        return 5;
    }
}

🚀🚀终极大招(OneOf库)

  • 事实上我们上面实现的就是OneOf库的核心代码与核心使用方式;
  • OneOf库自动为我们生成 OneOf<T0, ..., Tn>(最多支持 8 种类型)。
  • 生成器(Source Generator)辅助,减少样板代码,可以将范型数量扩展到8个以外。
  • 去OneOf库的官网学习: 官方github

👉 OneOf库使用方式

  • 通过 NuGet 安装:
csharp 复制代码
dotnet add package OneOf
  • 定义方法参数 using OneOf;
csharp 复制代码
public class Demo
{
    public void Print(OneOf<int, string> value)
    {
        value.Switch(
            i => Console.WriteLine($"这是 int: {i}"),
            s => Console.WriteLine($"这是 string: {s}")
        );
    }
}

使用:

ini 复制代码
var demo = new Demo();

demo.Print(123);
demo.Print("hello");

Print 方法既能接受 int 又能接受 string,而且调用时 无需显式转换

  • 作为返回值
csharp 复制代码
using OneOf;

public class Parser
{
    public OneOf<int, string> Parse(string input)
    {
        if (int.TryParse(input, out var number))
            return number;  // 隐式转换成 OneOf<int, string>
        else
            return input;   // 也可以返回 string
    }
}

调用:

ini 复制代码
var parser = new Parser();
var result = parser.Parse("123");

result.Switch(
    i => Console.WriteLine($"解析成功: {i}"),
    s => Console.WriteLine($"解析失败,原始字符串: {s}")
);

如果需要表达式形式,就用 Match

csharp 复制代码
string Describe(OneOf<int, string> value) =>
    value.Match(
        i => $"数字 {i}",
        s => $"文本 '{s}'"
    );

Console.WriteLine(Describe(42));      // 输出 "数字 42"
Console.WriteLine(Describe("world")); // 输出 "文本 'world'"
相关推荐
程序员cxuan7 小时前
速度太快了!本地可以跑 DeepSeek-V4-Flash 了
人工智能·后端·程序员
阿祖zu8 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才8 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒9 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20239 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei9 小时前
controller注册及调用时机
后端
edwarddamon9 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈10 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas10010 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
计算机魔术师11 小时前
传统基础架构 - 微服务
大数据·后端·微服务·架构·开发工具·编程语言