3

我看到了打开(非)可空引用类型的巨大优势,但是我有很多带有可选参数的方法,我想知道纠正编译器产生的警告的正确方法是什么。

通过使用注释类型使参数可以为空,?从而消除了所有优点。另一个想法是将所有带有可选参数的方法变成单独的方法,这需要大量的工作并且会产生很高的复杂性(参数组合的指数爆炸)。

我正在考虑这样的事情,但我真的怀疑这是否是一种好方法(性能方面等),而不是第一眼:

[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);
  }
}
4

1 回答 1

1

这看起来像 F# 的选项。这可以在 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 错误阻止了这一点。链接的问题实际上试图创建一个基于抽象基类的 Option 类这已在 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# 选项

无论我们使用哪种技术,我们都可以将扩展方法添加到模仿 F# 的Option 模块Option的静态类中,例如 Bind,也许是最有用的方法,将函数应用于具有值的 Option 并返回 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会将方法应用于 Option 以创建 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);

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

于 2019-11-01T17:28:54.270 回答