在尝试可空引用类型部分The issue with T?
中 说明了警告的原因。长话短说,如果您使用T?
必须指定类型是类还是结构。您可能最终为每种情况创建两种类型。
更深层的问题是,使用一种类型来实现Result并同时保留Success和Error值会带来Result应该解决的相同问题,还有更多问题。
- 相同类型必须携带一个无效值(类型或错误),或者带回null
- 类型上的模式匹配是不可能的。您必须使用一些精美的位置模式匹配表达式才能使其正常工作。
- 为了避免空值,您必须使用Option / Maybe之类的东西,类似于F#的Options。但是,无论是值还是错误,您仍然会带一个None。
F#中的结果(或两者)
起点应该是F#的Result类型和已区分的并集。毕竟,这已经可以在.NET上使用了。
F#中的结果类型为:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
类型本身只能满足需要。
F#中的DU允许详尽的模式匹配而无需null:
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
在C#8中模拟
不幸的是,C#8还没有DU,它们已经安排用于C#9。在C#8中,我们可以模拟它,但是我们失去了详尽的匹配:
#nullable enable
public interface IResult<TResult,TError>{}
struct Success<TResult,TError> : IResult<TResult,TError>
{
public TResult Value {get;}
public Success(TResult value)=>Value=value;
public void Deconstruct(out TResult value)=>value=Value;
}
struct Error<TResult,TError> : IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error)=>ErrorValue=error;
public void Deconstruct(out TError error)=>error=ErrorValue;
}
并使用它:
IResult<double,string> Sqrt(IResult<double,string> input)
{
return input switch {
Error<double,string> e => e,
Success<double,string> (var v) when v<0 => new Error<double,string>("Negative"),
Success<double,string> (var v) => new Success<double,string>(Math.Sqrt(v)),
_ => throw new ArgumentException()
};
}
没有详尽的模式匹配,我们必须添加该默认子句以避免编译器警告。
我仍在寻找一种在不引入无效值的情况下进行详尽匹配的方法,即使它们只是一个选择。
选项/也许
通过使用穷举匹配的方式创建Option类更加简单:
readonly struct Option<T>
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option<T> Some<T>(T value)=>new Option<T>(value);
public static Option<T> None<T>()=>default;
}
可以用于:
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};