Это похоже на 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);
Это упрощает создание других вспомогательных функций или функций цепочки, которые выдают опции