想启用可为空的引用类型时如何处理可选参数? [英] How to deal with optional arguments when wanting to enable nullable reference types?

查看:231
本文介绍了想启用可为空的引用类型时如何处理可选参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I see the great advantage of turning on (non-)nullable reference types, but I have quite a few methods with optional parameters and I am wondering what the right way to correct the warnings yielded by the compiler is.

通过使用?注释类型使参数可为空,从而消除了所有好处.另一个想法是将所有带有可选参数的方法转换为单独的方法,这需要大量工作,并且会产生很高的复杂度(参数组合呈指数级增长).

Making the parameter nullable by annotating the type with ? takes all of the goodness away. Another idea is to turn all methods with optional parameters into separate methods, which is quite a lot of work and yields high complexity (exponential explosion of parameter combinations).

我当时正在考虑这样的事情,但是我真的怀疑这是否是一眼就能看到的好方法(在性能方面):

I was thinking about something like this, but I really question if that it a good approach (performance-wise etc.) beyond the first glance:

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

推荐答案

这看起来像F#的Option.可以在C#8中使用模式匹配表达式进行仿真.这个结构:

This look's like F#'s Option. This can be emulated in C# 8 up to a point with pattern matching expressions. This struct :

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,而第二个选项使用位置模式匹配来通过解构函数实际提取值.

The first option uses property pattern matching to check for None, while the second one uses positional pattern matching to actually extract the value through the deconstructor.

令人高兴的是,编译器将其识别为穷举匹配,因此我们无需添加默认子句.

The nice thing is that the compiler recognizes this as an exhaustive match so we don't need to add a default clause.

不幸的是,罗斯林(Roslyn)错误防止了此情况.链接的问题实际上试图基于抽象基类创建Option class .这是在VS 2019 16.4预览1中修复的.

Unfortunately, a Roslyn bug prevents this. The linked issue actually tries to create an Option class based on an abstract base class. This was fixed in VS 2019 16.4 Preview 1.

固定编译器允许我们省略参数或传递None:

The fixed compiler allows us to omit the parameter or pass a 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同时发布.

VS 2019 16.4 should come out at the same time as .NET Core 3.1 in a few weeks.

在此之前,更丑陋的解决方案可能是在解构函数中返回IsSome并在两种情况下都使用位置模式匹配:

Until then, an uglier solution could be to return IsSome in the deconstructor and use positional pattern matching in both cases:

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#的

No matter which technique we use, we can add extension methods to the Option static class that mimic F#'s Option module, eg Bind, perhaps the most useful method, applies a function to an Option if it has a value and returns an Option, or returns None if there's no value :

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:

For example this applies the Format method to an Option to create a 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);

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

This makes it easy to create other helper functions, or chain functions that produce options

这篇关于想启用可为空的引用类型时如何处理可选参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆