5.4.1 在 C# 中实现选项类型

728 篇文章 1 订阅
349 篇文章 0 订阅

5.4.1 在 C# 中实现选项类型

 

正如我们所看到的,在函数式编程中,选项(option)类型非常重要,我们也希望能够在 C# 中进行函数风格编程,因此,需要在C# 中实现适当的选项类型。我们已经讨论过如何用面向对象语言实现差别联合,这里代码的结构类似于我们前面讨论过的Schedule 类型。在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 中可以看到源代码。

 

清单 5.9 使用类的泛型 option 类型 (C#)

enum OptionType { Some, None };    [1]

 

abstract class Option<T> {

  privatereadonly OptionType tag;

  protectedOption(OptionType tag) {

    this.tag= tag;

  }

  publicOptionType Tag { get { return tag; } }  <--指定选项类型

}

class None<T> : Option<T> {    [2]

  publicNone() : base(OptionType.None) { }

}

class Some<T> : Option<T> {    [3]

  publicSome(T value) : base(OptionType.Some) {

    this.value= value;

  }

  privatereadonly T value;

  publicT Value { get { return value; } }  <--带有实际值

}

static class Option {    [4]

  publicstatic Option<T> None<T>() { <-- 创建空的选项

    returnnew None<T>();

  }

  publicstatic Option<T> Some<T>(T value) { <-- 创建有值的选项

    returnnew Some<T>(value);

  }

}

 

泛型基类只包含 Tag 属性,它取值可能是由枚举 OptionType [1]描述的两个值中的一个;设置 tag 值在两个派生类 None<T> [2]和 Some<T> [3]的构造函数中实现;第二个派生类携带值,因此,有一个T 类型的属性 Value,像通常的函数编程一样,这个属性是不可变的,因此,它只在构造函数中设置一次。

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

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

 

Option<int> ReadInput() {

  strings = Console.ReadLine();

  intparsed;

  if(Int32.TryParse(s, out parsed))

    returnOption.Some(parsed);

  else

    returnOption.None<int>();

}

 

由于我们使用新的 Option<T> 类,方法可以返回一个结果,可能包含值,也可能不包含值。在我们看到如何使用返回值之前,要先给Option<T> 类添加两个有用的方法。在 F# 中,我们使用模式匹配进行选项判断;清单 5.10 中的方法说明,在 C# 中也可以写类似的代码。

 

清单 5.10 Option 类的模式匹配方法 (C#)

public bool MatchNone() {

  returnTag == OptionType.None;  <-- 值为 None 时返回 True

}

public bool MatchSome(out T value) {

  if(Tag == OptionType.Some) value = ((Some<T>)this).Value;  [1]

  elsevalue = default(T);

  returnTag == OptionType.Some;

}

 

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

 

清单 5.11 使用 option 类型 (C#)

void TestInput() {

  Option<int>inp = ReadInput();

  intparsed;

  if(inp.MatchSome(out parsed))  <-- Some 模式

    Console.WriteLine("Youentered: {0}", parsed);

  elseif (inp.MatchNone())  <-- None 模式

    Console.WriteLine("Incorrectinput!");

}

 

由于有了 MatchSome 和 MatchNone 的工具函数,我们不必显式强制把值转换成派生类(例如,Some<T>)才能访问值这个值;然而,它仍缺乏模式匹配很多有用的功能。编译器不能验证我们提供的代码老辣了所有分支;更重要的是,不能写嵌套模式,而这是 F# 中常见的技巧。例如,可能要创建包含元组的选项类型,可以写成简单的 Some(1, "One"),使用 match 构造的模式,可以直接从元组 Some(num, str) 中读取值。

 

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

 

如果你熟悉面向对象编程,可能注意到,我们刚才实现的 Option<T> 类没有遵循面向对象的最佳做法;特别是,我们使用 Tag 属性作为有扩展方法的类型代码,而没有使用虚方法和多态(polymorphism)。

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

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

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

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值