我已经使用结果类很多年了,当我接触 C# 时,我第一次遇到的是 John Ottosson 结果类。这是我用作自己目的基础的结果类的版本,多年来一直为我服务。但它并非没有缺陷,我花了很多时间试图找出更优雅的方法来让它发挥作用。
这是 C# 结果类的典型实现。
public class Error
{
public string Message { get; set; }
//left out other properties for brevity
}
public class Result
{
//If there are no errors, then it is a successful call
public bool IsSuccessful => Errors?.Any() ?? true;
public IReadOnlyList<Error>? Errors { get; set; }
public static Result<T> Success<T>(T data)
{
return new Result<T>
{
Data = data,
Errors = null
};
}
public static Result<T> Fail<T>(Error error)
{
return new Result<T>
{
Errors = new List<Error> { error }
};
}
}
public class Result<T> : Result
{
public T Data { get; set; }
}
最让我烦恼的是,你必须为 Fail 方法指定一个类型,即使 Fail 方法只会导致 T 为 null。
我知道这是一个挑剔的问题,但这是我多年来一直想知道的问题。它的使用很烦人,而且不得不写
Result.Fail<MyDto?>("Some Error Message")
有点误导。如果有人确实有更好的方法,我很想听听。我已经将这个课程转移到打字稿几次只是为了业余项目,它在那里工作得更好。
更新我通过使用 OneOf 取得了一些进展(感谢@Alejandro)
这无法编译,但我可能越来越接近了。
public class Result
{
//cut for brevity
public static Result<EmptyResponse> Fail(Error error)
{
return new Result<EmptyResponse>
{
Errors = new List<Error> { error },
Data = new EmptyResponse() //Errors here. See error below
};
}
}
public class Result<T> : Result
{
public OneOf<T, EmptyResponse> Data { get; set; }
}
ERROR: Cannot convert source type 'Playground.EmptyResponse' to target type 'OneOf.OneOf<Playground.EmptyResponse,Playground.EmptyResponse>
即使 Data 是 OneOf T 或 EmptyResponse。它仍然需要提供 T 的类型信息。因此,虽然上面的代码无法编译,但是这会
public static Result<T> Fail<T>(Error error)
{
return new Result<T>
{
Errors = new List<Error> { error },
Data = new EmptyResponse()
};
}
这基本上又回到了第一个方面。向不需要的 Fail 提供类型信息。
为什么不直接从
Result
工厂方法返回 Fail
呢?或者,您可以在类型上使用模式匹配以获得更实用的体验,并从成功结果中删除 Errors
,就像从失败结果中删除 Data
一样:
https://dotnetfiddle.net/IFYFHj
public interface IResult
{
}
public record SuccessResult : IResult
{
public static readonly SuccessResult Instance = new();
}
public record SuccessResult<TData>( TData Data ) : SuccessResult;
public record FailureResult : IResult
{
public static readonly FailureResult Instance = new();
}
public record FailureResult<TError>( IEnumerable<TError> Errors ) : FailureResult;
用途:
var msg = result switch
{
SuccessResult<int> sri => $"Successful with data {sri.Data}",
SuccessResult sr => $"Successful with no data",
FailureResult<int> fri => $"Failed with errors",
FailureResult fr => $"Failed without errors",
_ => throw new NotImplementedException( result.GetType().FullName ),
};