Update
If you did改变用法,并转换IsSuccess
对于属性,您可以摆脱可空性问题and获得详尽的匹配。这个开关表达式是详尽的,即编译器可以检查是否满足了所有可能性。它确实要求每个分支仅检索有效的属性:
var message=result switch { {IsSuccess:true,Data:var data} => $"Got some: {data}",
{IsSuccess:false,Description:var error} => $"Oops {error}",
};
如果你的方法接受并返回IResult<T>
对象,你可以这样写:
IResult<string> Doubler(IResult<string> input)
{
return input switch { {IsSuccess:true,Data:var data} => new Ok<string>(data+ "2"),
{IsSuccess:false} => input
};
}
...
var result2=new Ok<string>("3");
var message2=Doubler(result2) switch {
{IsSuccess:true,Data:var data} => $"Got some: {data}",
{IsSuccess:false,Description:var error} => $"Oops {error}",
};
原答案
它看起来像real问题是执行Result https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/results图案。这种模式有两个特点:
- 它防止在类型级别使用无效结果值。它通过使用两种不同的类型来表示好的结果和坏的结果来实现这一点。通过这样做,每种类型仅携带其所需的内容。
- 它强制客户端处理所有情况或明确忽略它们。
有些语言(例如 Rust)为此提供了内置类型。支持选项类型/可区分联合的函数式语言(如 F#)只需执行以下操作即可轻松实现:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
详尽的模式匹配意味着客户端必须处理这两种情况。不过这种类型很常见,它已经融入了语言本身。
C# 8
在 C# 8 中,我们可以实现这两种类型,而无需进行详尽的模式匹配。目前,这些类型需要一个公共类,无论是接口还是抽象类,它实际上不需要任何成员。有很多方法可以实现它们,例如:
public interface IResult<TSuccess,TError>{}
public class Ok<TSuccess,TError>:IResult<TSuccess,TError>
{
public TSuccess Data{get;}
public Ok(TSuccess data)=>Data=data;
public void Deconstruct(out TSuccess data)=>data=Data;
}
public class Fail<TSuccess,TError>:IResult<TSuccess,TError>
{
public TError Error{get;}
public Fail(TError error)=>Error=error;
public void Deconstruct(out TError error)=>error=Error;
}
我们可以使用结构而不是类。
或者,要使用更接近 C# 9 的可区分联合的语法,可以嵌套类。类型仍然可以是接口,但是我真的不喜欢写new IResult<string,string>.Fail
或命名接口Result
代替IResult
:
public abstract class Result<TSuccess,TError>
{
public class Ok:Result<TSuccess,TError>
{
public TSuccess Data{get;}
public Ok(TSuccess data)=>Data=data;
public void Deconstruct(out TSuccess data)=>data=Data;
}
public class Fail:Result<TSuccess,TError>
{
public TError Error{get;}
public Fail(TError error)=>Error=error;
public void Deconstruct(out TError error)=>error=Error;
}
//Convenience methods
public static Result<TSuccess,TError> Good(TSuccess data)=>new Ok(data);
public static Result<TSuccess,TError> Bad(TError error)=>new Fail(error);
}
我们可以使用模式匹配来处理Result
价值观。不幸的是,C# 8 不提供详尽的匹配,因此我们还需要添加默认情况。
var result=Result<string,string>.Bad("moo");
var message=result switch { Result<string,string>.Ok (var Data) => $"Got some: {Data}",
Result<string,string>.Fail (var Error) => $"Oops {Error}"
_ => throw new InvalidOperationException("Unexpected result case")
};
C# 9
C# 9(可能)将通过添加可区分联合枚举类 https://github.com/dotnet/csharplang/blob/master/proposals/discriminated-unions.md。我们将能够写:
enum class Result
{
Ok(MySuccess Data),
Fail(MyError Error)
}
并通过模式匹配来使用它。只要有匹配的解构函数,此语法就已经可以在 C# 8 中使用。 C# 9 将添加详尽的匹配,并且可能还会简化语法:
var message=result switch { Result.Ok (var Data) => $"Got some: {Data}",
Result.Fail (var Error) => $"Oops {Error}"
};
通过 DIM 更新现有类型
一些现有的功能,例如IsSuccess
and Outcome
只是方便的方法。事实上,F# 的选项类型还将值的“种类”公开为tag。我们可以将此类方法添加到接口中并从实现中返回固定值:
public interface IResult<TSuccess,TError>
{
public bool IsSuccess {get;}
public bool IsFailure {get;}
public bool ResultOutcome {get;}
}
public class Ok<TSuccess,string>:IResult<TSuccess,TError>
{
public bool IsSuccess =>true;
public bool IsFailure =>false;
public bool ResultOutcome =>ResultOutcome.Success;
...
}
The Description
and Data
属性也可以作为权宜之计来实现——它们破坏了结果模式,而模式匹配无论如何都会使它们过时:
public class Ok<TSuccess,TError>:IResult<TSuccess,TError>
{
...
public TError Description=>throw new InvalidOperationException("A Success Result has no Description");
...
}
默认接口成员可用于避免乱七八糟的具体类型:
public interface IResult<TSuccess,TError>
{
//Migration methods
public TSuccess Data=>
(this is Ok<TSuccess,TError> (var Data))
?Data
:throw new InvalidOperationException("An Error has no data");
public TError Description=>
(this is Fail<TSuccess,TError> (var Error))
?Error
:throw new InvalidOperationException("A Success Result has no Description");
//Convenience methods
public static IResult<TSuccess,TError> Good(TSuccess data)=>new Ok<TSuccess,TError>(data);
public static IResult<TSuccess,TError> Bad(TError error)=>new Fail<TSuccess,TError>(error);
}
修改以添加详尽匹配
如果我们只使用,我们可以避免模式匹配异常中的默认情况one标志和迁移属性:
public interface IResult<TSuccess,TError>
{
public bool IsSuccess{get;}
public bool IsFailure=>!IsSuccess;
//Migration methods
...
}
var message2=result switch { {IsSuccess:true,Data:var data} => $"Got some: {data}",
{IsSuccess:false,Description:var error} => $"Oops {error}",
};
这次,编译器检测到只有两种情况,并且都被覆盖。迁移属性允许编译器检索正确的类型。消费代码必须改变and使用正确的模式,但我怀疑它已经起作用了