您是否曾经需要一种方法,能够根据不同的情况返回不同类型的返回值呢?比如,支付处理工具可能需要返回多种支付方式;订单状态可能包含多种信息;或者文件加载器需要处理多种格式的文件。
在C#中,我们通常通过继承层次结构、标记接口或包装对象来解决这个问题——但这些方式都会增加代码的复杂性,同时也会降低类型安全性。不过幸运的是,还有更好的解决方案:使用OneOf库。
如果您之前使用过TypeScript进行编程,那么您可能已经熟悉了联合类型这个概念。联合类型并不是C#原生支持的功能,但未来会实现这一功能。在此之前,您可以继续使用OneOf库。
在这篇文章中,我将向您展示如何使用OneOf库来实现类似F#中的判别性联合类型,从而让代码更加简洁、易读且具有更强的类型安全性,适用于各种场景——从多态的返回值到状态机,甚至优雅的错误处理。
目录
什么是OneOf?
OneOf库提供了判别性联合类型,使得您可以从一个方法中返回多个预定义的类型之一。与
可以将其视为一种类型安全的方式来表示:“该方法返回类型A、类型B或类型C”。编译器会确保您处理了所有可能性。
// 而不是这样(无论是否需要,都返回两者)
public (User user, Error error) GetUser(int id) { ... }
// 您可以这样做(只返回其中一个)
public OneOf<User, NotFound, DatabaseError> GetUser(int id) { ... }
为什么需要OneOf
-
类型安全性: 编译器确保您处理了所有可能的返回值类型
-
自文档化: 方法签名清楚地显示了所有可能的结果
-
不需要继承: 可以返回不同的类型,而无需将它们强制放入类层次结构中
-
模式匹配: 使用<.Match()》来处理每种情况
-
灵活性: 可以根据需要支持2、3、4种以上的返回类型
安装OneOf
选项1(推荐):
使用终端导航到项目文件夹,然后运行以下命令:
dotnet add package OneOf
选项2:
使用您的IDE(Visual Studio、Rider或VS Code):
-
右键点击项目文件
-
选择“管理NuGet包”
-
搜索“OneOf”
-
点击“安装”
核心概念和功能
为了充分利用OneOf库并理解其真正的好处,您需要了解几个核心概念。
联合类型:众多中的一种
本质上,OneOf代表了一种联合类型。这种类型可以在任何时候只包含几种预定义类型的其中之一。可以将其视为一种类型安全的容器,它只能存储一种类型的值,但这种值可以是您指定的任何一种类型。
// 这个变量可以存储字符串、整数或布尔值中的一个
// 但是一次只能存储其中一种
OneOf<string, int, bool> myValue;
myValue = "hello"; // 当前存储的是字符串
myValue = 42; // 现在存储的是整数
myValue = true; // 现在存储的是布尔值
这与C#的
// Tuple:同时存储所有值(并且是同时发生的)
var tuple = ("hello", 42, true); // 包含了字符串、整数和布尔值
// OneOf:每次只存储一种值(或者几种值中的一种)
OneOf<string, int, bool> union = "hello"; // 只包含字符串、整数或布尔值中的一种
类型安全性和全面的处理
OneOf不仅使用方便,而且由编译器强制执行。当您使用OneOf值时,编译器会确保您处理了所有可能的类型。这避免了因为忽略某些情况而导致的错误。
例如:
OneOf<Success, Failure> result = GetResult();
// 编译器会强制你处理所有三种情况
result.Match(
success => Ok(success.Value),
failure => HandleFailure(failure.Type
switch
(
ErrorType.NotFound
=> NotFound(new[]{"User {id} not found"}),
ErrorType.Database
=> StatusCode(500, new[]{"Something went wrong"})
)
);
为什么会更好呢?
-
它提供了一种统一的通知接口
-
每个渠道都有精确的参数需求
-
没有可选或不必要的字段
-
清晰的路由逻辑
用例1:多态的返回类型(无需继承)
当您需要根据逻辑返回不同类型的数据时,可以使用OneOf来轻松处理这种情况。
public OneOf<CsvData, JsonData, ExcelData> LoadDataFile(string path)
{
var extension = Path.GetExtension(path).ToLower();
return extension switch
{
". csv" => new CsvData(File.ReadAllLines(path)),
".json" => new JsonData(file.Content),
".xlsx" => new ExcelData(file.Workbook),
_ => throw new UnsupportedFileFormatException(extension)
};
这对于以下情况非常适用:
-
提供多种导出格式的API
-
接受多种文件的导入向导
-
支持多种格式的配置加载器
OneOf的主要优势
当有以下情况时,OneOf表现得最为出色:
-
存在多个有效的返回类型,而这些类型之间没有共同的基类
-
针对不同场景有不同的数据形态
-
类型安全的分支处理,让编译器强制要求处理所有情况
-
领域建模中,不同状态携带不同的信息
-
明确的结果,这些结果应该是方法签名的一部分
本质上,OneOf是一种以类型安全的方式表示“此方法返回A或B或C”的方法,从而迫使调用者必须明确处理每一种情况。这样可以使代码更加健壮、易于维护,同时也减少了出错的可能性。
如您所见,如果您喜欢阅读这篇文章,请随时在Twitter上联系我吧。



