想要启用可空引用类型时如何处理可选参数?
Posted
技术标签:
【中文标题】想要启用可空引用类型时如何处理可选参数?【英文标题】:How to deal with optional arguments when wanting to enable nullable reference types? 【发布时间】:2020-02-27 03:12:00 【问题描述】:我看到了打开(非)可空引用类型的巨大优势,但我有很多带有可选参数的方法,我想知道纠正编译器产生的警告的正确方法是什么。
通过使用?
注释类型来使参数可以为空,这将带走所有优点。另一个想法是将所有带有可选参数的方法变成单独的方法,这需要大量的工作并且会产生很高的复杂性(参数组合的指数爆炸式增长)。
我正在考虑这样的事情,但我真的怀疑这是否是一种很好的方法(性能方面等),而不是第一眼:
[Fact]
public void Test()
Assert.Equal("nothing", Helper().ValueOrFallbackTo("nothing"));
Assert.Equal("foo", Helper("foo").ValueOrFallbackTo("whatever"));
public static Optional<string> Helper(Optional<string> x = default)
return x;
public readonly ref struct Optional<T>
private readonly bool initialized;
private readonly T value;
public Optional(T value)
initialized = true;
this.value = value;
public T ValueOrFallbackTo(T fallbackValue)
return initialized ? value : fallbackValue;
public static implicit operator Optional<T>(T value)
return new Optional<T>(value);
【问题讨论】:
“所有的善良”?如果参数是可选的,它应该可以为空。这几乎不意味着代码中的每个引用都必须是可选参数。实际上,可为空是可选的,不可为空是非-可选的。我们保留将可空引用定义为非默认行为的能力,因为虽然它可以说是一个糟糕的默认行为,但有时它肯定很有用。这就是为什么我们有Nullable<T>
用于值类型。
可选的非空结构参数已经可用 - 只需在签名中添加一个默认值。这在 C# 8 中没有改变。对于引用类型,Option<T>
会很有用,但不需要所有代码。只是可以与模式匹配表达式一起使用的东西。
首先,你说的可选是什么意思?在调用站点省略参数?还是显式传递 None
值?
@Panagiotis:我的意思是可选的,允许在调用站点省略参数。
【参考方案1】:
这看起来像 F# 的 Option。这可以在 C# 8 中使用模式匹配表达式进行模拟。这个结构:
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)=>(value)=(Value);
//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;
...
应该允许这样的代码:
static string Test(Option<MyClass> opt = default)
return opt switch
Option<MyClass> IsNone: true => "None",
Option<MyClass> (var v) => $"Some v.SomeText",
;
第一个选项使用属性模式匹配来检查None
,而第二个选项使用位置模式匹配通过解构器实际提取值。
好消息是编译器将其识别为穷举匹配,因此我们不需要添加默认子句。
不幸的是,一个 Roslyn 错误 prevents this。链接的问题实际上试图创建一个基于抽象基类的 Option class。这是fixed in VS 2019 16.4 Preview 1。
固定的编译器允许我们省略参数或传递None
:
class MyClass
public string SomeText get; set; = "";
...
Console.WriteLine( Test() );
Console.WriteLine( Test(Option.None<MyClass>()) );
var c = new MyClass SomeText = "Cheese" ;
Console.WriteLine( Test(Option.Some(c)) );
这会产生:
None
None
Some Cheese
VS 2019 16.4 应该会在几周内与 .NET Core 3.1 同时发布。
在那之前,一个更丑的解决方案可能是在解构器中返回IsSome
,并在两种情况下都使用位置模式匹配:
public 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);
public void Deconstruct(out T value)=>(value)=(Value);
还有
return opt switch Option<MyClass> (_ ,false) =>"None",
Option<MyClass> (var v,true) => $"Some v.SomeText" , ;
借用 F# 选项
无论我们使用哪种技术,我们都可以向Option
静态类添加模仿 F# 的 Option module 的扩展方法,例如 Bind,也许是最有用的方法,如果它有一个值,则将一个函数应用于一个 Option 并且返回一个选项,如果没有值则返回 None :
public static Option<U> Bind<T,U>(this Option<T> inp,Func<T,Option<U>> func)
return inp switch Option<T> (_ ,false) =>Option.None<U>(),
Option<T> (var v,true) => func(v) ,
;
例如,这会将 Format
方法应用于选项以创建 Optino:
Option<string> Format(MyClass c)
return Option.Some($"Some c.SomeText");
var c=new MyClass SomeText = "Cheese";
var opt=Option.Some(c);
var message=opt.Bind(Format);
这使得创建其他辅助函数或产生选项的链函数变得容易
【讨论】:
以上是关于想要启用可空引用类型时如何处理可选参数?的主要内容,如果未能解决你的问题,请参考以下文章