想要启用可空引用类型时如何处理可选参数?

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&lt;T&gt; 用于值类型。 可选的非空结构参数已经可用 - 只需在签名中添加一个默认值。这在 C# 8 中没有改变。对于引用类型,Option&lt;T&gt; 会很有用,但不需要所有代码。只是可以与模式匹配表达式一起使用的东西。 首先,你说的可选是什么意思?在调用站点省略参数?还是显式传递 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);

这使得创建其他辅助函数或产生选项的链函数变得容易

【讨论】:

以上是关于想要启用可空引用类型时如何处理可选参数?的主要内容,如果未能解决你的问题,请参考以下文章

构造准备好的语句时如何处理可选列

Go中的make函数如何处理可选参数? [复制]

在bash中使用getopts来获取可选的输入参数[重复]

何时在启用可空引用类型的情况下对参数进行空检查

JavaScript:数组的sort()排序(遇到负数时如何处理)

如何处理 Play 框架中的可选查询参数