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 许可证授权。

编辑推荐精选

Keevx

Keevx

AI数字人视频创作平台

Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。

即梦AI

即梦AI

一站式AI创作平台

提供 AI 驱动的图片、视频生成及数字人等功能,助力创意创作

扣子-AI办公

扣子-AI办公

AI办公助手,复杂任务高效处理

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

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

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

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

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

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

讯飞星火

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

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

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

Spark-TTS

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

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

下拉加载更多