- 给它一个星星 ⭐!
- 入门 🏃
- 创建
ErrorOr
实例 - 属性
- 方法
- 混合使用功能(
Then
、FailIf
、Else
、Switch
、Match
) - 错误类型
- 内置结果类型(
Result.Success
,..) - 组织错误
- Mediator + FluentValidation +
ErrorOr
🤝 - 贡献 🤲
- 致谢 🙏
- 许可证 🪪
给它一个星星 ⭐!
喜欢它吗?通过给这个项目一个星星来表示你的支持!
入门 🏃
用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/Else和Switch/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
实例
使用隐式转换
有从TResult
、Error
、List<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
方法接收两个函数,onValue
和onError
,如果结果成功则调用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
方法接收两个函数,onValue
和onError
,如果结果成功则调用onValue
,如果结果是错误则调用onError
。
与Match
不同,如果状态是错误,MatchFirst
的onError
函数只接收发生的第一个错误,而不是整个错误列表。
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
方法接收两个动作,onValue
和 onError
,如果结果成功则调用 onValue
,如果结果是错误则调用 onError
。
与 Switch
不同,如果状态是错误,SwitchFirst
的 onError
函数只接收发生的第一个错误,而不是整个错误列表。
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}"));
ThenDo
和 ThenDoAsync
ThenDo
和 ThenDoAsync
类似于 Then
和 ThenAsync
,但它们调用的是动作而不是返回值的函数。
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
, Switch
和 Match
方法。
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 许可证授权。