在我们的实际开发中已经会遇到可空类型,而在C#中自从2.0之后就提供了可空类型(Nullable<T>),普通的值类型是不可以赋值为NULL,但是在类型的后面加上问号就变成了可空类型,这样就可以赋值为NULL了。当然这样的方式也可以用于函数式编程中,但函数式编程有自己的独特方式来解决这种问题,今天我们将围绕这个问题,虽然篇幅比较少,但也请读者可以阅读完。
我们当然不能改变语言的设计,所以我们只能使用现有的来实现可选值。这里我们利用类来实现,下面是Option<T>初期的代码:
1 public sealed class Option<T> 2 { 3 private readonly T value; 4 public T Value 5 { 6 get 7 { 8 return value; 9 } 10 } 11 12 private readonly bool hasValue; 13 public bool HasValue { get { return hasValue; } } 14 public bool IsSome { get { return hasValue; } } 15 public bool IsNone { get { return !hasValue; } } 16 17 public Option(T value) 18 { 19 this.value = value; 20 this.hasValue = true; 21 } 22 23 private Option() { } 24 public static readonly Option<T> None = new Option<T>(); 25 }
这样我们就可以利用这个类来实现和可空类型一样的效果,比如下面这段代码:
1 var p1 = new Option<int>(10); 2 var p2 = Option<int>.None; 3 if (p1.HasValue) // 等价于 x != null 4 { 5 6 }
我们可以看到在实例化Option的时候还需要传递类型参数,这里我们可以通过一个技巧来避免输入类型参数,而依赖于类型推断,这里我们写个辅助类:
1 public sealed class Option 2 { 3 public static Option<T> Some<T>(T value) 4 { 5 return new Option<T>(value); 6 } 7 }
其实我们只是利用了一个泛型方法来创建这个实例就可以了,下面我们看看下面的示例:
1 static void Main(string[] args) 2 { 3 var p1 = Option.Some(10); 4 var p2 = Option.Some(10); 5 if (p1 == p2) 6 { 7 Console.WriteLine("Y"); 8 } 9 Console.ReadKey(); 10 }
这里我们看到Option.Some节省了一些功夫,但是读者如果运行上面这个例子会发现,“Y”并不会输出到控制台中,但是我们可以看到10本来就应该等于10。因为这里我们对比的是Option<T>类型,并且p1和p2是两个不同的Option<T>所以我们还需要加以改善,下面我们重载操作符(在Option<T>类中继续追加):
1 public static bool operator ==(Option<T> a, Option<T> b) 2 { 3 return a.HasValue == b.HasValue && EqualityComparer<T>.Default.Equals(a.Value, b.Value); 4 } 5 public static bool operator !=(Option<T> a, Option<T> b) 6 { 7 return !(a == b); 8 } 9 10 public override int GetHashCode() 11 { 12 int hashCode = hasValue.GetHashCode(); 13 if (hasValue) 14 hashCode ^= value.GetHashCode(); 15 return hashCode; 16 }
这里我们通过重载“==”判断其中的Value,这样就可以解决上面的问题了。但是我们还有一个问题没有解决,就是在使用None的时候还需要传递类型参数,这里我们依然需要使用一个技巧来避免(在Option类中继续追加):
public static readonly Option None = new Option();
仅仅这样还不足够,因为Option无法转换成Option<T>类型所以我们还需要对Option<T>增加 功能,能够隐式的将Option转换成对应的Option<T>类型,下面是针对的代码(在Option<T>中增加):
1 public static implicit operator Option<T>(Option option) 2 { 3 return Option<T>.None; 4 }
这样我们就可以直接将Option与Option<T>进行对比了,到这里我们就完成了我们自己的可选值的实现,在具体的项目中是使用可空类型还是可选值完全可以根据你个人的喜好,笔者建议使用本节介绍的可选值。