error-or

error-or

优化C#错误处理和结果管理

ErrorOr是一个C#库,采用discriminated union模式简化错误处理。它支持多错误返回,提供丰富的功能方法,可替代异常抛出。通过链式调用处理结果,ErrorOr简化了错误处理逻辑,提高了代码可读性和可维护性。这个库适用于多种开发场景,为C#开发者提供了更优雅的错误管理方式。

ErrorOr错误处理函数式编程C#异常处理Github开源项目
<div align="center">

<img src="https://yellow-cdn.veclightyear.com/b77ea3a9/6a034d11-82b2-4ec8-b1b4-096f277b9b6d.png" alt="drawing" width="700px"/></br>

NuGet

Build publish ErrorOr to nuget

GitHub contributors GitHub Stars GitHub license codecov

一个简单、流畅的错误或结果的区分联合。

dotnet add package ErrorOr

</div>

给它一个星星 ⭐!

喜欢它吗?通过给这个项目一个星星来表示你的支持!

入门 🏃

ErrorOr<T>替换抛出异常

这个 👇

public float Divide(int a, int b) { if (b == 0) { throw new Exception("不能除以零"); } return a / b; } try { var result = Divide(4, 2); Console.WriteLine(result * 2); // 4 } catch (Exception e) { Console.WriteLine(e.Message); return; }

变成这个 👇

public ErrorOr<float> Divide(int a, int b) { if (b == 0) { return Error.Unexpected(description: "不能除以零"); } return a / b; } var result = Divide(4, 2); if (result.IsError) { Console.WriteLine(result.FirstError.Description); return; } Console.WriteLine(result.Value * 2); // 4

或者,使用Then/ElseSwitch/Match,你可以这样做 👇

Divide(4, 2) .Then(val => val * 2) .SwitchFirst( onValue: Console.WriteLine, // 4 onFirstError: error => Console.WriteLine(error.Description));

支持多个错误

内部,ErrorOr对象有一个Error列表,所以如果你有多个错误,你不需要妥协只保留第一个。

public class User(string _name) { public static ErrorOr<User> Create(string name) { List<Error> errors = []; if (name.Length < 2) { errors.Add(Error.Validation(description: "名字太短")); } if (name.Length > 100) { errors.Add(Error.Validation(description: "名字太长")); } if (string.IsNullOrWhiteSpace(name)) { errors.Add(Error.Validation(description: "名字不能为空或只包含空白字符")); } if (errors.Count > 0) { return errors; } return new User(name); } }

各种功能性方法和扩展方法

ErrorOr对象有各种方法,允许你以功能性的方式使用它。

这允许你将方法链接在一起,并以清晰简洁的方式处理结果。

现实世界的例子

return await _userRepository.GetByIdAsync(id) .Then(user => user.IncrementAge() .Then(success => user) .Else(errors => Error.Unexpected("不期望失败"))) .FailIf(user => !user.IsOverAge(18), UserErrors.UnderAge) .ThenDo(user => _logger.LogInformation($"用户 {user.Id} 年龄增加到 {user.Age}")) .ThenAsync(user => _userRepository.UpdateAsync(user)) .Match( _ => NoContent(), errors => errors.ToActionResult());

带中间步骤的简单示例

无失败

ErrorOr<string> foo = await "2".ToErrorOr() .Then(int.Parse) // 2 .FailIf(val => val > 2, Error.Validation(description: $"{val} 太大了") // 2 .ThenDoAsync(Task.Delay) // 睡眠2毫秒 .ThenDo(val => Console.WriteLine($"等待 {val} 毫秒完成。")) // 等待2毫秒完成。 .ThenAsync(val => Task.FromResult(val * 2)) // 4 .Then(val => $"结果是 {val}") // "结果是4" .Else(errors => Error.Unexpected(description: "哎呀")) // "结果是4" .MatchFirst( value => value, // "结果是4" firstError => $"发生错误:{firstError.Description}");

失败

ErrorOr<string> foo = await "5".ToErrorOr() .Then(int.Parse) // 5 .FailIf(val => val > 2, Error.Validation(description: $"{val} 太大了") // Error.Validation() .ThenDoAsync(Task.Delay) // Error.Validation() .ThenDo(val => Console.WriteLine($"等待 {val} 毫秒完成。")) // Error.Validation() .ThenAsync(val => Task.FromResult(val * 2)) // Error.Validation() .Then(val => $"结果是 {val}") // Error.Validation() .Else(errors => Error.Unexpected(description: "哎呀")) // Error.Unexpected() .MatchFirst( value => value, firstError => $"发生错误:{firstError.Description}"); // 发生错误:哎呀

创建ErrorOr实例

使用隐式转换

有从TResultErrorList<Error>ErrorOr<TResult>的隐式转换器

ErrorOr<int> result = 5; ErrorOr<int> result = Error.Unexpected(); ErrorOr<int> result = [Error.Validation(), Error.Validation()];
public ErrorOr<int> IntToErrorOr() { return 5; }
public ErrorOr<int> SingleErrorToErrorOr() { return Error.Unexpected(); }
public ErrorOr<int> MultipleErrorsToErrorOr() { return [ Error.Validation(description: "无效的名字"), Error.Validation(description: "无效的姓氏") ]; }

使用ErrorOrFactory

ErrorOr<int> result = ErrorOrFactory.From(5); ErrorOr<int> result = ErrorOrFactory.From<int>(Error.Unexpected()); ErrorOr<int> result = ErrorOrFactory.From<int>([Error.Validation(), Error.Validation()]);
public ErrorOr<int> GetValue() { return ErrorOrFactory.From(5); }
public ErrorOr<int> SingleErrorToErrorOr() { return ErrorOrFactory.From<int>(Error.Unexpected()); }
public ErrorOr<int> MultipleErrorsToErrorOr() { return ErrorOrFactory.From([ Error.Validation(description: "无效的名字"), Error.Validation(description: "无效的姓氏") ]); }

使用ToErrorOr扩展方法

ErrorOr<int> result = 5.ToErrorOr(); ErrorOr<int> result = Error.Unexpected().ToErrorOr<int>(); ErrorOr<int> result = new[] { Error.Validation(), Error.Validation() }.ToErrorOr<int>();

属性

IsError

ErrorOr<int> result = User.Create(); if (result.IsError) { // 结果包含一个或多个错误 }

Value

ErrorOr<int> result = User.Create(); if (!result.IsError) // 结果包含一个值 { Console.WriteLine(result.Value); }

Errors

ErrorOr<int> result = User.Create(); if (result.IsError) { result.Errors // 包含发生的错误列表 .ForEach(error => Console.WriteLine(error.Description)); }

FirstError

ErrorOr<int> result = User.Create(); if (result.IsError) { var firstError = result.FirstError; // 只有第一个发生的错误 Console.WriteLine(firstError == result.Errors[0]); // true }

ErrorsOrEmptyList

ErrorOr<int> result = User.Create(); if (result.IsError) { result.ErrorsOrEmptyList // List<Error> { /* 一个或多个错误 */ } return; } result.ErrorsOrEmptyList // List<Error> { }

方法

Match

Match方法接收两个函数,onValueonError,如果结果成功则调用onValue,如果结果是错误则调用onError

Match

string foo = result.Match( value => value, errors => $"发生了 {errors.Count} 个错误。");

MatchAsync

string foo = await result.MatchAsync( value => Task.FromResult(value), errors => Task.FromResult($"发生了 {errors.Count} 个错误。"));

MatchFirst

MatchFirst方法接收两个函数,onValueonError,如果结果成功则调用onValue,如果结果是错误则调用onError

Match不同,如果状态是错误,MatchFirstonError函数只接收发生的第一个错误,而不是整个错误列表。

string foo = result. ```cs 错误 => Console.WriteLine($"发生了 {errors.Count} 个错误。"));

SwitchAsync

await result.SwitchAsync( value => { Console.WriteLine(value); return Task.CompletedTask; }, errors => { Console.WriteLine($"发生了 {errors.Count} 个错误。"); return Task.CompletedTask; });

SwitchFirst

SwitchFirst 方法接收两个动作,onValueonError,如果结果成功则调用 onValue,如果结果是错误则调用 onError

Switch 不同,如果状态是错误,SwitchFirstonError 函数只接收发生的第一个错误,而不是整个错误列表。

result.SwitchFirst( value => Console.WriteLine(value), firstError => Console.WriteLine(firstError.Description));

SwitchFirstAsync

await result.SwitchFirstAsync( value => { Console.WriteLine(value); return Task.CompletedTask; }, firstError => { Console.WriteLine(firstError.Description); return Task.CompletedTask; });

Then

Then

Then 接收一个函数,并且仅在结果不是错误时调用它。

ErrorOr<int> foo = result .Then(val => val * 2);

可以链式调用多个 Then 方法。

ErrorOr<string> foo = result .Then(val => val * 2) .Then(val => $"结果是 {val}");

如果任何方法返回错误,链式调用将中断并返回错误。

ErrorOr<int> Foo() => Error.Unexpected(); ErrorOr<string> foo = result .Then(val => val * 2) .Then(_ => GetAnError()) .Then(val => $"结果是 {val}") // 这个函数不会被调用 .Then(val => $"结果是 {val}"); // 这个函数不会被调用

ThenAsync

ThenAsync 接收一个异步函数,并且仅在结果不是错误时调用它。

ErrorOr<string> foo = await result .ThenAsync(val => DoSomethingAsync(val)) .ThenAsync(val => DoSomethingElseAsync($"结果是 {val}"));

ThenDoThenDoAsync

ThenDoThenDoAsync 类似于 ThenThenAsync,但它们调用的是动作而不是返回值的函数。

ErrorOr<string> foo = result .ThenDo(val => Console.WriteLine(val)) .ThenDo(val => Console.WriteLine($"结果是 {val}"));
ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .ThenDo(val => Console.WriteLine($"等待 {val} 秒完成。")) .ThenDoAsync(val => Task.FromResult(val * 2)) .ThenDo(val => $"结果是 {val}");

混合使用 Then, ThenDo, ThenAsync, ThenDoAsync

你可以混合搭配使用 Then, ThenDo, ThenAsync, ThenDoAsync 方法。

ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .Then(val => val * 2) .ThenAsync(val => DoSomethingAsync(val)) .ThenDo(val => Console.WriteLine($"等待 {val} 秒完成。")) .ThenAsync(val => Task.FromResult(val * 2)) .Then(val => $"结果是 {val}");

FailIf

FailIf 接收一个谓词和一个错误。如果谓词为真,FailIf 将返回错误。否则,它将返回结果的值。

ErrorOr<int> foo = result .FailIf(val => val > 2, Error.Validation(description: $"{val} 太大了"));

一旦返回错误,链式调用将中断并返回错误。

var result = "2".ToErrorOr() .Then(int.Parse) // 2 .FailIf(val => val > 1, Error.Validation(description: $"{val} 太大了") // 验证错误 .Then(num => num * 2) // 这个函数不会被调用 .Then(num => num * 2) // 这个函数不会被调用

Else

Else 接收一个值或一个函数。如果结果是错误,Else 将返回该值或调用该函数。否则,它将返回结果的值。

Else

ErrorOr<string> foo = result .Else("后备值");
ErrorOr<string> foo = result .Else(errors => $"发生了 {errors.Count} 个错误。");

ElseAsync

ErrorOr<string> foo = await result .ElseAsync(Task.FromResult("后备值"));
ErrorOr<string> foo = await result .ElseAsync(errors => Task.FromResult($"发生了 {errors.Count} 个错误。"));

混合使用功能 (Then, FailIf, Else, Switch, Match)

你可以混合使用 Then, FailIf, Else, SwitchMatch 方法。

ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .FailIf(val => val > 2, Error.Validation(description: $"{val} 太大了")) .ThenDo(val => Console.WriteLine($"等待 {val} 秒完成。")) .ThenAsync(val => Task.FromResult(val * 2)) .Then(val => $"结果是 {val}") .Else(errors => Error.Unexpected()) .MatchFirst( value => value, firstError => $"发生错误: {firstError.Description}");

错误类型

每个 Error 实例都有一个 Type 属性,它是一个枚举值,表示错误的类型。

内置错误类型

以下是内置的错误类型:

public enum ErrorType { Failure, Unexpected, Validation, Conflict, NotFound, Unauthorized, Forbidden, }

每种错误类型都有一个静态方法来创建该类型的错误。例如:

var error = Error.NotFound();

可选地,你可以为错误传递代码、描述和元数据:

var error = Error.Unexpected( code: "User.ShouldNeverHappen", description: "永远不应该发生的用户错误", metadata: new Dictionary<string, object> { { "user", user }, });

ErrorType 枚举是对错误进行分类的好方法。

自定义错误类型

如果你想以不同方式对错误进行分类,可以创建自己的错误类型。

可以使用 Custom 静态方法创建自定义错误类型

public static class MyErrorTypes { const int ShouldNeverHappen = 12; } var error = Error.Custom( type: MyErrorTypes.ShouldNeverHappen, code: "User.ShouldNeverHappen", description: "永远不应该发生的用户错误");

你可以使用 Error.NumericType 方法来获取错误的数字类型。

var errorMessage = Error.NumericType switch { MyErrorType.ShouldNeverHappen => "考虑更换开发团队", _ => "发生未知错误。", };

内置结果类型 (Result.Success, ..)

有几种内置的结果类型:

ErrorOr<Success> result = Result.Success; ErrorOr<Created> result = Result.Created; ErrorOr<Updated> result = Result.Updated; ErrorOr<Deleted> result = Result.Deleted;

可以如下使用

ErrorOr<Deleted> DeleteUser(Guid id) { var user = await _userRepository.GetByIdAsync(id); if (user is null) { return Error.NotFound(description: "未找到用户。"); } await _userRepository.DeleteAsync(user); return Result.Deleted; }

组织错误

一个不错的方法是创建一个包含预期错误的静态类。例如:

public static partial class DivisionErrors { public static Error CannotDivideByZero = Error.Unexpected( code: "Division.CannotDivideByZero", description: "不能除以零。"); }

之后可以这样使用 👇

public ErrorOr<float> Divide(int a, int b) { if (b == 0) { return DivisionErrors.CannotDivideByZero; } return a / b; }

Mediator + FluentValidation + ErrorOr 🤝

使用 MediatR 时的一种常见方法是使用 FluentValidation 在请求到达处理程序之前对其进行验证。

通常,验证是通过一个在请求无效时抛出异常的 Behavior 来完成的。

使用 ErrorOr,我们可以创建一个返回错误而不是抛出异常的 Behavior

当项目使用 ErrorOr 时,这种方式很好,因为调用 Mediator 的层,与项目中的其他组件类似,只需接收一个 ErrorOr 并相应地处理它。

这里是一个验证请求并在请求无效时返回错误的 Behavior 示例 👇

public class ValidationBehavior<TRequest, TResponse>(IValidator<TRequest>? validator = null) : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : IErrorOr { private readonly IValidator<TRequest>? _validator = validator; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { if (_validator is null) { return await next(); } var validationResult = await _validator.ValidateAsync(request, cancellationToken); if (validationResult.IsValid) { return await next(); } var errors = validationResult.Errors .ConvertAll(error => Error.Validation( code: error.PropertyName, description: error.ErrorMessage)); return (dynamic)errors; } }

贡献 🤲

如果你有任何问题、评论或建议,请开启一个 issue 或创建一个 pull request 🙂

致谢 🙏

  • OneOf - 一个很棒的库,为 C# 提供了 F# 风格的区分联合行为

许可证 🪪

本项目基于 MIT 许可证授权。

编辑推荐精选

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

热门AI工具AI办公办公工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

模型训练热门AI工具内容创作智能问答AI开发讯飞星火大模型多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

Trae

Trae

字节跳动发布的AI编程神器IDE

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

热门AI工具生产力协作转型TraeAI IDE
咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

AI助手热门AI工具AI创作AI辅助写作讯飞绘文内容运营个性化文章多平台分发
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

Hunyuan3D-2

Hunyuan3D-2

高分辨率纹理 3D 资产生成

Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。

3FS

3FS

一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。

3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。

下拉加载更多