c# - 可空引用类型和任一模式
问题描述
我正在C# 8.0 中尝试新的 Nullable Reference Types,我正面临以下问题。
鉴于此结构:
public readonly struct Either<TReturn, TError>
where TReturn : struct
where TError : struct
{
public TError? Error { get; }
public TReturn? Response { get; }
public Either(TError? error, TReturn? response)
{
if (error == null && response == null)
{
throw new ArgumentException("One argument needs not to be null.");
}
if (error != null && response != null)
{
throw new ArgumentException("One argument must be null.");
}
Error = error;
Response = response;
}
}
我如何告诉编译器其中一个 Error
或 Response
不为空,并且它们不能都为空?有没有办法用新属性做这样的事情?
解决方案
结构更新
当结果类型更改为结构时,代码不会更改。要使用结构类型参数,必须将以下约束添加到接口和类型:
where TResult : struct
where TError : struct
当我想到 Either 模式时,我想到了 F#、模式匹配和有区别的联合,而不是 null。实际上,Either
是一种避免空值的方法。事实上,问题的代码看起来像是在尝试创建一个Result 类型,而不仅仅是一个 Either。Scott Wlaschin 的Railway Oriented Programming展示了如何使用这种类型在函数式语言中实现错误处理。
在 F# 中,Result类型定义为:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
我们还不能在 C# 8 中这样做,因为没有可区分的联合。这些是为 C# 9 计划的。
模式匹配
我们可以做的是使用模式匹配来获得相同的行为,例如:
interface IResult<TResult,TError>{} //No need for an actual implementation
public class Success<TResult,TError>:IResult<TResult,TError>
{
public TResult Result {get;}
public Success(TResult result) { Result=result;}
}
public class Error<TResult,TError>:IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error) { ErrorValue=error;}
}
这样就无法创建IResult<>
既成功又错误的方法。这可以与模式匹配一起使用,例如:
IResult<int,string> someResult=.....;
if(someResult is Success<int,string> s)
{
//Use s.Result here
}
简化表达式
鉴于 C# 8 的属性模式,这可以重写为:
if(someResult is Success<int,string> {Result: var result} )
{
Console.WriteLine(result);
}
或者,使用 switch 表达式,一个典型的铁路风格调用:
IResult<int,string> DoubleIt(IResult<int,string> data)
{
return data switch { Error<int,string> e=>e,
Success<int,string> {Result: var result}=>
new Success<int,string>(result*2),
_ => throw new Exception("Unexpected type!")
};
}
F# 不需要它,throw
因为 an不可能是orResult<'T,'TError>
以外的东西。在 C# 中,我们还没有这个功能。Ok
Error
switch 表达式允许穷举匹配。我认为如果默认子句也丢失,编译器会生成警告。
使用解构器
如果类型具有解构函数,则表达式可以进一步简化,例如:
public class Success<TResult,TError>:IResult<TResult,TError>
{
public TResult Result {get;}
public Success(TResult result) { Result=result;}
public void Deconstruct(out TResult result) { result=Result;}
}
public class Error<TResult,TError>:IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error) { ErrorValue=error;}
public void Deconstruct(out TError error) { error=ErrorValue;}
}
在这种情况下,表达式可以写成:
return data switch {
Error<int,string> e => e,
Success<int,string> (var result) => new Success<int,string>(result*3),
_ => throw new Exception("Unexpected type!")
};
可空性
问题从可空引用类型开始,那么可空性呢?如果我们尝试传递 nulll,我们会在 C# 8 中收到警告吗?
可以,只要启用 NRT。这段代码:
#nullable enable
void Main()
{
IResult<string,string> data=new Success<string,string>(null);
var it=Append1(data);
Console.WriteLine(it);
}
IResult<string,string> Append1(IResult<string,string> data)
{
return data switch { Error<string,string> e=>e,
Success<string,string> (var result)=>
new Success<string,string>(result+"1"),
_ => throw new Exception("Unexpected type!")
};
}
生成CS8625: Cannot convert null literal to non-nullable reference type
试
string? s=null;
IResult<string,string> data=new Success<string,string>(s);
生成CS8604: Possible null reference argument ....
推荐阅读
- c - 如何修复这个简单的 do/while 循环?
- regex - PowerShell RegEx:从字符串中获取 SID
- ios - 当 iOS 应用程序被暂停/杀死并且用户点击通知时如何处理 Firebase 推送通知?
- sql - 显示子级最高层次父级
- kotlin - 如何从 Try in arrow-kt 中抽象出来
- swift - 将 Swift { get set } 属性覆盖为仅 { get } 属性。使变量成为常数
- scala - 如何将 from_json 标准函数与自定义模式一起使用(错误:使用替代方法重载方法值 from_json)?
- python - 摆脱显式超级
- java - 如何添加drawerLayout而不破坏现有的LinerLayout样式?
- javascript - 选项卡在 Bootstrap v4.3.1 中不起作用,但在 v4.1.0 中起作用