摘要:我在介绍Visual Basic 9.0的时候,曾经多次提到Tuple这个概念,当时是作为匿名类型的实例出现的。现在我们单独来讨论一下这个概念。Tuple常常译为“组元”,在大部分支持Tuple的语言中,常常表示成员数目确定,每个成员类型也确定的结构。常常用于表示函数的多个返回值或者查询的结果等。Tuple应当是强类型的,即所有成员的类型在编译时确定。比如,假想语法下
Dim t = New Tuple(Of String, Integer, Double)
那么t将具有三个成员,该数目无法改变;同时三个成员的类型分别为String, Integer和Double,也无法改变。如你所见,Tuple可以看作不用事先声明的结构体,可以根据所使用的场合灵活地创建。那么VB9和C#3的匿名类型当然是Tuple很好的实现方案。但是这都是N年后的东西了,我们在.NET 2.0中能否实现Tuple?最关键的难点在于,我们要在希望使用的地方创建Tuple的结构,而不是事先声明,因此就必须有个灵活的机制来完成。
方法一:TypeList
我是某一天在公共汽车上想到这个办法,后来看到和Loki的TypeList有相似之处。当然.NET没有特化和记录类型的能力,所以无法实现TypeList。但我们把静态类型运算的思路移到运行时,就可以做Typed Variable List——那就是Tuple。
public abstract class TypeNode { internal TypeNode {} }public sealed class Tail : TypeNode { }public sealed class Tuple<T, TNode> : TypeNode where TNode : TypeNode, new(){ public T Field = default(T); public TNode Next = new TNode();}
我充分利用了.NET泛型的约束特性来达成我的设计。TypeNode被设计为abstract,因此约束了new()的泛型参数TNode将无法取值TypeNode本身的类型。而其internal的构造函数又限制了用户继承于它。这个手法就将TNode的取值范围限定在Tail和Tuple两个类型上。这个用法是我认为约束用法中相当巧妙的一种。
这个类型的原理很简单,就是利用泛型,在创建TypeList的实例时自动生成相同结构的链表。比如我们要创建一个String, Integer, Double的Tuple,就是这样写:
Tuple<string, Tuple<int, Tuple<double, Tail>>> t;
如你所见,这种Tuple的类型参数第一个是某节点的类型,第二个要么是另一个Tuple,要么是Tail(表示终结列表)。这个对象创建出来以后就会自动生成一个“各个节点类型都不相同”的链表。
t = new Tuple<string, Tuple<int, Tuple<double, Tail>>>();t.Field = " a string ";t.Next.Field = 123;t.Next.Next.Field = 13.56;
Tail没有Next字段,因此遇到Tail就代表Tuple终结了,这可以由编译器检查,因此没有越界的危险。而且这种Tuple可以达到无限长。不过这种方法也是有缺陷的,首先使用的语法方面非常不便,如果要用第7个字段,要写成myTuple.Next.Next.Next.Next.Next.Next.Field,稍不注意就会写错。无论VB还是C#都没有足够的抽象能力简化这一操作。第二个缺陷是建立Tuple时的一连串new操作开销很大,因为这里的new是通过反射进行的。所以受限于语言特性的缺乏,这种方法无法达到很完美的地步,不过这个思路也许在其他场合可以用上。
方法二:重载原型
模仿泛型委托的思路,我们可以用完全泛型化的一系列同名结构来模拟即时创建的Tuple:
struct Tuple<T0>{ public T0 Field0;}struct Tuple<T0, T1>{ public T0 Field0; public T1 Field1;}struct Tuple<T0, T1, T2>{ public T0 Field0; public T1 Field1; public T2 Field2;}......struct Tuple<T0, T1, T2, T3, T4, T5, T6,......[
阅读全文]