5.4.1 在 C# 中实现选项类型

本文介绍了如何在C#中实现选项类型,包括定义泛型类Option<T>及其派生类Some<T>和None<T>。通过使用这些类,可以在C#中模拟函数式编程中的选项类型行为。

5.4.1 在 C# 中实现选项类型

正如我们所看到的,在函数的编程中,选项类型是很重要的,我们也希望能够在 C# 中用函数风格代码,因此,需要选项类型的适当 C# 实现。我们已经讨论了如何在面向对象的语言中对差别联合进行编码,因此,该代码的结构类似于我们刚才讨论过的计划类型。在Option<T> 情况下,我们可以创建有 HasValue 属性的单个类(或值类型),有点简单。然而,我们想要演示在一般情况下,差别联合的编码概念,所以,我们将创建一个基类,Option<T>,有 Tag 属性,并为两个可能的可选值创建的派生类。

提示

我们将在后面的章节中使用这个类型,所以,我们也将增加几个工具方法,使常规 C# 编程中使用更方便。这会使代码稍长,因此,你可以从本书的网站下载。此外,还可以直接从 http://www.functional-programming.net/library 下载 .NET 库,除了这个以外,还有这本书讨论的其他一上些类,或者从 Manning http://www.manning.com/Real-WorldFunctionalProgramming,有代码包可用。

要使这个类型可重用,我们将把它实现为 C# 的泛型类,Option<T>。派生类 Some<T>,表示一个可选值,有 T 类型的值,None <T> 类,表示没有值的可选值。可以看到清单 5.9 中的源代码 。

Listing 5.9 Generic option type using classes (C#)

enum OptionType { Some, None };
abstract class Option<T> {
private readonly OptionType tag;
protected Option(OptionType tag) {
this.tag = tag;
}
public OptionType Tag { get { return tag; } }
}
class None<T> : Option<T> {
public None() : base(OptionType.None) { }
}
class Some<T> : Option<T> {
public Some(T value) : base(OptionType.Some) {
this.value = value;
}
private readonly T value;
public T Value { get { return value; } }
}
static class Option {
public static Option<T> None<T>() {
return new None<T>();
}
public static Option<T> Some<T>(T value) {
return new Some<T>(value);
}
}

泛型基类只包含 Tag 属性,可以有由枚举 OptionType 指定两个值之一。在两个派生的类 None<T> 和 Some<T> 的构造函数中设置 tag。第二个派生类携带一个值,因此,它有一个叫 Value 的属性,是 T类型的。像往常一样,在函数编程中,此属性是不可变的,所以,它只在构造函数中设置一次。

该代码还包括一个非泛型的工具类,Option。我们已经在第三章中实现了相似的类,当时,在 C# 中实现函数式的元组和列表类型。此类的目的是简化选项值的结构。不直接使用构造函数(new Some<int>(10)),我们可以利用 C# 类型推断,当调用泛型方法,写 Option.Some(10) 时。

现在,我们如何在 C# 中使用我们的 Option<T>?下面的代码片断显示了来自清单 5.7 代码的 C# 版本,尝试从控制台读取数字:

Option<int> ReadInput() {
string s = Console.ReadLine();
int parsed;
if (Int32.TryParse(s, out parsed))
return Option.Some(parsed);
else
return Option.None<int>();
}

由于我们使用新的 Option<T> 类,该方法可以返回单个结果,这可能会,也可能不包含值。在我们看如何可以使用返回值之前,要先添加两个有用的方法到 Option<T> 类。在 F# 中,我们使用模式匹配来分别告诉选项;清单 5.10 中的方法让我们在 C# 中写类似的代码。

Listing 5.10 Pattern-matching methods for the Option class (C#)

public bool MatchNone() {
return Tag == OptionType.None;
}
public bool MatchSome(out T value) {
if (Tag == OptionType.Some) value = ((Some<T>)this).Value;
else value = default(T);
return Tag == OptionType.Some;
}

两个方法都返回一个布尔值,告诉我们这个实例是否表示被测试可选值。第二个还有一个输出参数,当这个对象是 Some 类的实例时,它被设置为携带 Option<T> 类型的值;否则,输出参数设置为默认值,即,返回 false。清单 5.11 显示了我们如何使用ReadInput 法,使用了两个工具方法。

Listing 5.11 Working with the option type (C#)

void TestInput() {
Option<int> inp = ReadInput();
int parsed;
if (inp.MatchSome(out parsed))
Console.WriteLine("You entered: {0}", parsed);
else if (inp.MatchNone())
Console.WriteLine("Incorrect input!");
}

多亏了 MatchSome 和 MatchNone 的实用程序,我们不必显式强制把值转换继承的类(例如,Some<T>)才能访问值这个值。然而,它仍缺乏模式匹配的很多有用的功能。编译器不会验证我们正在提供代码的所有分支。更重要的是,不能写嵌套的模式,这在 F# 中常见的技巧。例如,你可能要创建选项类型,携带一个元组。这应该写成简单的 Some(1, "One"),使用 match 构造的模式,可以直接从元组中读取值:Some(num, str)。

差别联合和面向对象的原则

如果你是有经验的面向对象的程序员,可能注意到,我们刚实现的 Option<T> 类没有遵循面向对象的最佳做法。特别,我们使用 Tag 属性作为类型代码,有扩展方法,而不是使用虚拟方法和多态性。

使用类型代码的第一个原因是教育。在 F# 中,以非常类似的方式,差别联合被编译成 .NET 的程序集代码。当你在 F# 中声明差别联合,编译器将创建单个基类,有类型代码,为每种差别联合情况有一个派生类。使用种差别联合的代码,比如模式匹配,首先确定哪个派生类,它得到了作为参数值。然后,它可以把这个实例强制转换为特定的类,访问种差别联合情况的参数。

第二个原因是为什么我们不使用虚拟方法,是这个 Option<T>类型,如大多数的差别联合,不是一种典型的面向对象的类。它设计成不是可扩展的,这意味着,我们不指望任何人可以添加新的情况。我们也不必要更改 OptionType 枚举。

如果我们想避免类型代码,可以把 MatchNone 和 MatchSome 实现为虚拟方法。这是可行,因为,这两个方法显示有关类层次的完整信息,就像类型代码。在下一章,我们将需要完整的信息,我们会在其中添加使用选项的几个方法。这些方法将更多的其他工具,比一个固有部分的类型,所以,我们会要使用扩展方法来实现它们。

现在,你已经看到,在 C# 中如何使用泛型实现选项类型,我们可以把注意力转回到 F#,显示了在 F# 库中如何声明内置的选项类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值