这看起来像 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);
这使得创建其他辅助函数或生成选项的链函数变得容易