可以在 C# 中使用联合类型,但是,由于 C# 没有真正意义上的联合类型,因此,在 C# 中使用看上去不漂亮。在这一节,我们将讨论如何在 C# 中使用联合类型,以及作为库设计人员,如何可以决定库是否公开联合类型(虽然,我个人建议避免跨语言公开联合类型)。
第一个例子,我们定义了一个简单的联合类型Quantity,它有两个构造函数,一个包含整数,另一个包含浮点数;还提供一个函数getRandomQuantity(),初始化 Quantity 的新实例。
module Strangelights.DemoModule
open System
// type that can represent a discrete or continuousquantity
type Quantity =
| Discrete of int
| Continuous of float
// initalize random number generator
let rand =
new Random()
// create a random quantity
letgetRandomQuantity() =
match rand.Next(1)
with
| 0
->Quantity.Discrete (rand.Next())
| _ ->
Quantity.Continuous
(rand.NextDouble() * float_of_int (rand.Next()))
虽然,我们提供了getRandomQuantity() 去创建 Quantity 类型的新版本,但是,类型自身也提供了静态方法,去创建组成这个类型的不同构造函数的新实例。这些静态方法在所有联合类型上都可用,它缺省由程序集公开,不需要做任何特别的事情,让编译器去创建它。下面的例子演示了如何从 C# 中使用这些方法:
using System;
using Strangelights;
static class
GetQuantityZeroClass
{
public
static void GetQuantityZero()
{
// initialize both aDiscrete and Continuous quantity
DemoModule.Quantity d =
DemoModule.Quantity.Discrete(12);
DemoModule.Quantity c =
DemoModule.Quantity.Continuous(12.0);
}
}
现在,我们已经知道如何从 C# 中创建联合类型,因此,下面最重要的任务就是决定构造函数属于哪一个特定的Quantity 值。可以有三种方法,下面的两段代码,我先讨论前两种方法,第三种方法要放在本节的最后。
第一种方法是可以对值的Tag 属性的分支选择(switch)。这个属性是整数,但是,因为联合类型的编译版本提供的是常量,总是有tag_ 前缀,帮助识别这个整数的含义。因此,如果想使用Tag 属性去找出是哪种类型的Quantity,通常写一个switch 语句,如下面的例子所示:
//!!! C# Source !!!
using System;
using Strangelights;
static class
GetQuantityOneClass
{
public
static void GetQuantityOne()
{
// get a randomquantity
DemoModule.Quantity q =
DemoModule.getRandomQuantity();
// use the .Tagproperty to switch over the quatity
switch (q.Tag)
{
case
DemoModule.Quantity.tag_Discrete:
Console.WriteLine("Discretevalue: {0}",q.Discrete1);
break;
case
DemoModule.Quantity.tag_Continuous:
Console.WriteLine("Continuousvalue: {0}",q.Continuous1);
break;
}
}
}
这个例子的运行的结果如下:
Discrete value: 65676
如果我们愿意,联合类型的编译形式也提供了一系列方法,前缀都是Is,用测试一个值是否属于联合类型中的特定构造函数。例如,在联合类型Quantity 中,有两个方法,IsDiscrete() 和IsContinuous(),测试Quantity 是属于Discrete,还是Continuous。下面的例子就演示了如何使用:
//!!! C# Source !!!
using System;
using Strangelights;
static class
GetQuantityTwoClass
{
public
static void GetQuantityTwo()
{
// get a randomquantity
DemoModule.Quantity q =
DemoModule.getRandomQuantity();
// use if ... elsechain to display value
if (q.IsDiscrete())
{
Console.WriteLine("Discretevalue: {0}",q.Discrete1);
}
else
if (q.IsContinuous())
{
Console.WriteLine("Continuousvalue: {0}",q.Continuous1);
}
}
}
示例的运行结果如下:
Discrete value: 2058
因为执行模式匹配所需的代码量过大,所以,这两种方法都不特别令人满意;且还有风险,如果写的代码如下所示,检测值是否是Discrete,但错误地使用了Continuous1 属性,就会出错,导致了NullReferenceException 异常。
DemoModule.EasyQuantity q =
DemoModule.getRandomEasyQuantity();
if (q.IsDiscrete())
{
Console.WriteLine("Discretevalue: {0}", q.Continuous1);
}
为给库用户提供更多的保障,为联合类型添加成员,执行模式匹配,是一个好办法。下面的例子修改了Quantity 类型,产生新类型EasyQuantity,添加两个成员,把类型转换成整数或浮点数:
module Strangelights.ImprovedModule
open System
//type that can represent a discrete or continuous quantity
//with members to improve interoperability
type
EasyQuantity =
|Discrete of
int
|Continuous of
float
// convert quantity to a float
member x.ToFloat() =
match x
with
| Discrete x -> float x
| Continuous x -> x
// convert quantity to a integer
member x.ToInt() =
match x
with
| Discrete x -> x
| Continuous x -> int x
//initalize random number generator
let rand =
new Random()
//create a random quantity
letgetRandomEasyQuantity() =
match rand.Next(1)
with
| 0 ->
EasyQuantity.Discrete (rand.Next())
| _ ->
EasyQuantity.Continuous
(rand.NextDouble() * float(rand.Next()))
这样,库用户既可把值转换成整数,也可以转换成浮点数,而不必担心模式匹配,就像下面的例子:
//!!! C# Source !!!
using System;
using Strangelights;
class
GetQuantityThreeClass
{
public
static void GetQuantityThree()
{
// get a randomquantity
ImprovedModule.EasyQuantity q =
ImprovedModule.getRandomEasyQuantity();
// convert quantityto a float and show it
Console.WriteLine("Value as afloat: {0}",q.ToFloat());
}
}
[
所有的示例都没有测试。
]