装配中的脑袋

用程序装配大脑,再用大脑装配程序
随笔 - 118, 评论 - 1214, 引用 - 11

导航

关于

如果想发较大的信件,请用Ninputer @ gmail.com

不要在我的Blog评论中张贴广告,除非同意向我付款。

标签

每月存档

最新留言

广告

探讨.NET 2.0中Tuple的实现方法

我在介绍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结构增加了一个初始化所有成员的构造函数。

posted on 2005-10-19 08:56:00 by Ninputer  评论(7) 阅读(5776)

Visual Basic 9.0 前沿播报·动态篇(三)XML字面量和XML后期绑定啊

本期是Visual Basic 9.0前沿播报动态篇的第三篇,也是这个系列的完结篇。按照惯例,我将Visual Basic 9.0最激动人心的部分放在了这一篇。在这一篇,我们将看到所有新特性作用在XML上之后带来的革新性变化。

Visual Basic 9.0专门为XML集成设置了一个开发组,力求将XML语言与Visual Basic语言完全结合,使Visual Basic成为全行业对XML支持最佳的开发工具。这些新特性都基于微软的XLinq——新一代支持语言集成查询的XML框架。使用XLinq可以按照XML逻辑树状对象模型来建立和访问。本文不打算介绍XLink,有兴趣可以下载这篇文档http://msdn.microsoft.com/VBasic/Future/XLinq%20Overview.doc。我要介绍的是,VB9为XLinq所做的惊人语法。

XML字面量

你有没有想过在一种编程语言里创建XML可以达到多么简单?VB9支持直接XML文档的原始结构作为字面量,也就是说,VB9里可以直接写:

Dim x = <Books><Book author="A. S. Tanenbaum">Mordern Operating Systems</Book></Books>

这么写,VB不仅仅吧它当作一个字符串,而是可以帮你做XML结构的检查,而且编译器了解这一XML的结构,因此会有智能感知的帮助。你可以将XML字面量分成多行编写,直到遇到根元素的终结(例子中的</Books>)才判断XML字面量写完,因此无须使用续行符。这也就是说,VB的XML可以表达一个以单元素为根的XML结构片断。如果XML字面量仅仅是这样,就没有多大意思了。我们可以让这个XML字面量中的数据动态化:

Dim rootName = "Books"
Dim b As New Book {Author := "A. S. Tanenbaum", Title := "Mordern Operating Systems", ID := 20 }

Dim x = <(rootName)><Book author=(b.Author)><%= b.Title %></Book></>

我们观察一下上面的代码,元素名称Books可以用变量来指定,只要在括号组成的“洞”填入即可,相应的关闭标记则变成</>这很重要,因为元素名称已经是动态决定的了,所以引入特定写法根据位置来关闭相应标记。我们看到,attribute的值也可以用洞来填入(attribute的名称也可以用相同语法),但是标记的Inner Text部分则不能使用洞,而是要使用<%= %>这种类似于ASP.NET数据绑定的语法,这称为代码嵌入。 这已经很强大并且激动人心了,不是吗?但还不够,我们可以把查询包含的语法嵌入到XML字面量中。假设我们有一组Book要生成:

Dim books = GetBooks() '假设用别的代码生成了Book类型的集合

Dim x = _
<(rootName)>
  <%= Select
    <Book author=(b.Author)><%= b.Title %></Book>
  From b In books Where b.ID > 5 %>
</>

我们用代码嵌入的手段,将类似于SQL的查询包含直接插入到XML字面量中去了。其结果很显然,是生成了<Books>下<Book>的列表。使用XML字面量,我们可以非常灵活地组合和生成我们想要得XML;也可以把XML直接拷贝粘贴到VB的IDE中,稍加修改而成为你自己的XML生成器。XML字面量还完善支持XML的namespace等特性。有了VB9,还需要XSLT吗?难说……

XML后期绑定

现在我们有了生成XML的手段,那么访问XML的手段呢?Visual Basic 9.0提供了XML后期绑定,我们可以用最简单的语法来访问XML。假设我们有刚才生成的Books文档,则可以用下列语法直接访问到它的某一个Book

Dim title = x.Books.Book(0).InnerText
Dim author = x.Books.Book.@author

这语法很类似于XPath,我们还可以做到XPath的//的功效:

For Each Dim book In x.Books...Book
    '遍历每一个Book元素
Next

只要3个点,就可以把Books下边所有层次的Book都找到,然后直接就可以遍历。当然我们还可以用查询包含,使用SQL语法来查询XML文档。总之,在Visual Basic 9.0中使用XML会是一种前所未有的体验。

现在Visual Basic 9.0的新特性已经介绍完了,这确实是一个有着巨大进步的版本,同时特别注重用户体验的改善,保持着简单易用的特点。

posted on 2005-10-12 09:23:00 by Ninputer  评论(13) 阅读(10587)

Visual Basic 9.0 前沿播报·动态篇(二)动态接口

在谈到动态语言与静态语言的区别时,有一句很经典的话:Static typing when possible, dynamic typing when needed。Visual Basic支持后期绑定和动态标识符,因此完全可以按照对象的运行时类型进行操作。只是后期绑定或动态标识符缺乏对参数或返回值类型的约束,仅仅按照成员的名称进行操作。举一个例子:在静态篇我们介绍过匿名类型,假设有3个匿名类型的实例(Tuple):

Dim a = New { Name := "Robbins", Age := 25 }
Dim b = New { Name := "Andrei", ID := 12, Address := "Beijing" }
Dim c = New { Name := New NameType("Mark"), Title := "PM" }

如你所见,三个Tuple具有不同的类型,由于匿名类型没有办法实现接口,因此没有办法用统一的语法操作a和b相同的属性Name。用后期绑定可以统一访问Name属性,但是c的Name属性与a,b的不同,但是后期绑定却能让他们用统一的方式访问,这就让动态的隐患扩大了。有没有既可以动态访问成员,又有部分强类型的约束呢?Visual Basic 9.0引入了动态接口。动态接口与普通接口不同,类型无需声明实现动态接口,只要具有相应动态接口定义的成员即可转换为动态接口类型。这个特性也称为鸭子类型判定。因为流传这样一种动态语言类型判断的手法:如果一个东西走路像鸭子,说话也像鸭子,那它就是鸭子。动态接口的定义需要用Dynamic修饰符:

Dynamic Interface IHasName
    Property Name As String
End Interface

这个接口规定了一个String类型的属性,名为Name。任何类型的实例,只要具有String类型的属性Name,就被推定为实现IHasName:

Dim a = New { Name := "Robbins", Age := 25 }
Dim b = New Button() {Name := "Button1", Text := "Button1" }

Dim ihn As IHasName = a
ihn.Name = "Haward"
ihn = b
ihn.Name = "Button2"

尽管a的匿名类型和Button类型都没有声明实现IHasName接口,但他们都有类型为String名为Name的属性,因此都可以使用IHasName接口。动态接口提供了有类型约束和IDE智能感知的后期绑定,再次提高了Visual Basic动态编程的能力。不过要提示一点,动态接口并非类型安全性的特性。

posted on 2005-10-09 08:53:00 by Ninputer  评论(9) 阅读(8288)

Powered by: Joycode.MVC引擎 0.5.2.0