我在介紹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, T7, T8, T9> {...}
這樣就創建了一組Tuple結構。因為名稱相同,在使用中不會察覺到存在10個類型,而是“要什麼有什麼”:
Tuple<string, int, double> t1;
Tuple<string, string> t2;
Tuple<int, int, int, int, float> t3;
......
這和我們一開始假想的語法一樣!而且沒有任何額外開銷,相當完美。但是它的元素數目有限,一開始定義了幾個就只能有幾個,好在一般不需要太多,10個夠用了。不過這樣生成的Tuple有點死板,似乎沒有什麼可以智能化的地方。
我將在我的VBF中采用第二種Tuple方案,斟酌後還是覺得它比較實際。唯一改動的地方就是為每個Tuple結構增加了一個初始化所有成員的構造函數。