Как бороться с необязательными аргументами при желании включить обнуляемые ссылочные типы? - PullRequest
0 голосов
/ 31 октября 2019

Я вижу очень большое преимущество включения (не) обнуляемых ссылочных типов, но у меня довольно много методов с необязательными параметрами, и мне интересно, как правильно исправлять предупреждения, выдаваемые компилятором вмудрым способом.

Делаем параметр обнуляемым, помечая тип с помощью "?"забирает все доброДругая идея состоит в том, чтобы превратить все методы с необязательными параметрами в отдельные методы, что является довольно трудоемким делом или приводит к высокой сложности (экспоненциальный рост комбинаций параметров).

Я думал о чем-то таком, но я действительно спрашиваюесли это хороший подход (с точки зрения производительности и т. д.) за пределами первого взгляда:

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

1 Ответ

0 голосов
/ 01 ноября 2019

Это похоже на F #'s 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 предотвращает это . Связанная проблема на самом деле пытается создать Option class на основе абстрактного базового класса. Это было исправлено в 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 # Дополнительный модуль , например, 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);

Это упрощает создание других вспомогательных функций или функций цепочки, которые выдают опции

...