Evil 域

当Evil遇上先知

导航

VB 9.0新特性之扩展方法(Extension Mehtods)

Posted on 2008-06-18 17:07  Saar  阅读(1098)  评论(1编辑  收藏  举报
VB 9.0中扩展方法特性,允许程序员在不修改、不继承现有类的前提下,给类添加自定义的方法。
在本文中,我们首先以Notebook类为例来看看如何使用扩展方法,然后,试着扩展一个系统现有的类,最后,参考一些巨人们关于扩展类使用的建议和注意点。有关Notebook类完整的代码,请大家参见VB 9.0新特性之局部类型推理(Local Type Inference)中的示例代码。
我们看到,Notebook类中,除去构造、释放资源相关的函数以外,剩下的方法就只有一个重写Object类的ToString()方法。相关代码如下:
1    Public Overrides Function ToString() As String
2        Return "Laptop " & Me.Brand & "(" & Me.Type & ") is " & Me.Weight.ToString() _
3        & "kg, and at the price of " & Me.Price.ToString(0.0)
4    End Function
当我们创建的一个Notebook的对象,并使用它的ToString()方法时,它会把自己的品牌、重量、价格等属性构成一个字符串并予以输出。例如:
Laptop Lenovo(T61) is 2.3kg, and at the price of 16000
现在,假设我需要把型号及价提取出来,中间加一个Tab,以供其它程序进行处理。这时,显然,比较合理的做法是给Notebook添加一个方法,例如叫GetPrintString() 来构造这样的字符串并返回。遇到这样的情况,扩展方法便有了其用武之地了。
首先,我们新建一个模块ExtNotebook。然后,引用System.Runtime.CompilerServices。最后,扩展一个GetPrintString方法。代码如下:
1Imports System.Runtime.CompilerServices
2Module ExtNotebook
3    <Extension()> _
4    Public Function GetPrintString(ByVal notebook As Notebook) As String
5        Dim result = notebook.Brand & vbTab & notebook.Price.ToString()
6        Return result
7    End Function

8End Module
扩展方法需要用<Extension()>标示,而Extendsion在System.Runtime.CompilerServices命名空间里,因此,在建立扩展模块后,我们做的第一步是引入命令空间;在<Extension()>标识后面,就跟普通方法一样写一个方法签名。其中,这个方法至少有一个不可省略的参数。这个参数在扩展方法中功能非同小可!它表明了扩展方法与类之间的关系。在这个示例中,因为这个参数notebook是Notebook类型的,所以,编译器就知道,要扩展的是Notebook类。所以,这个参数是什么类型,这个方法就会被扩展到对应类型的实例上。
扩展方法写好了,然后,我们如何来调用它呢?我们来看一段调用扩展方法的代码实例:
 1Imports NoteBooks.ExtNotebook
 2Module Module1
 3
 4    Sub Main()
 5        TryExtensionMehtod()
 6    End Sub

 7
 8    Sub TryExtensionMehtod()
 9        Dim nb = New Notebook With {.Brand = "Lenovo", .Price = "16000", .Type = "T61", .Weight = 2.3}
10        Console.WriteLine(nb.ToString())
11        Console.WriteLine(nb.GetPrintString())
12    End Sub

13
14End Module

15
第1行代码,我们把扩展方法所在的模块引入;
第11行代码,我们调用了Notebook对象nb的GetPrintString()方法来返回字符串。其输出结果为:Lenovo〈tab>  16000。在引入了扩展方法的模块后,这个扩展方法调时跟类中其它方法一样,VS2008同样对其提供了Intellisence,只不过,方法前面的小图标中有一个向下的小箭头,表明,这是一个扩展方法,它的优先级不高。

虽然这样子,但是,针对于这个例子,也许你会有一个疑问:为什么搞这么复杂,修改一下类,添一个方法不就了事了吗?嗯,那是因为这个例子中的类是自己写的。如果有一个类不是自己写的,但是要添加方法,恐怕你就没有办法修改类了。来看一个简单的例子。
比如,我们想要一个方法,把一个Integer对象的值直接输出到屏幕上,我们就可以针对Integer类扩展一个Print()方法。
1Imports System.Runtime.CompilerServices
2Module ExtInteger
3    <Extension()> _
4    Public Sub Print(ByVal aInt As Integer)
5        Console.WriteLine(aInt.ToString())
6    End Sub

7End Module
很显然,我们没有办法去修改Integer类,但是,通过样一个扩展,我们为Integer的实例添加了一个Print方法,用于将其输出到屏幕。调用方法也与其实例方法一致:
 1Imports NoteBooks.ExtInteger
 2Module Module1
 3
 4    Sub Main()
 5        TryExtensionMehtod()
 6    End Sub

 7
 8    Sub TryExtensionMehtod()
 9        'some other code
10        Dim nbPrice = 2000
11        nbPrice.Print()
12        '
13
14    End Sub

15
16End Module

17
第10行,我们通过局部类型推理,得到了Integer的对象nbPrice,并且其值为2000,下一步,我们就调用扩展方法Print()将2000输出到屏幕上。

以上我们进行的扩展方法,在调用时都不带有参数。下面,来试试带参数的形式。让我们对上述一例小做修改,在屏幕上输出Integer对象的值的N倍,N在调用时传入。
 1Imports System.Runtime.CompilerServices
 2Module ExtInteger
 3    <Extension()> _
 4    Public Sub Print(ByVal aInt As Integer)
 5        Console.WriteLine(aInt.ToString())
 6    End Sub

 7
 8    <Extension()> _
 9    Public Sub Print(ByVal aInt As IntegerByVal n As Integer)
10        Console.WriteLine(aInt * n)
11    End Sub

12End Module
其中第9行有两个参数。第一个参数如前面所述,用于关联扩展方法要扩展的类,第二个参数则作为调用方法的第一个参数使用。看一下调用方法就清楚了。
 1Imports NoteBooks.ExtInteger
 2Module Module1
 3
 4    Sub Main()
 5        TryExtensionMehtod()
 6    End Sub

 7
 8    Sub TryExtensionMehtod()
 9        
10        Dim nbPrice = 2000
11        nbPrice.Print()
12        nbPrice.Print(2)
13        
14    End Sub

15
16End Module

17
第12行调用了我们后来扩展的第二个方法。
因此,我们看到,调用方法的参数总比扩展方法的参数少1个,因为扩展方法的第1个参数,用于确定扩展方法与要扩展的类之间的关系。

以上我们学会了如何对类进行方法的扩展,相信大家触类旁通,会想到对其它数据类型的扩展,那么,在VB 9.0里,到底有哪些数据结构可以进行方法的扩展呢?在此列出一个List:

类(引用类型)
结构(值类型)
接口
委托
ByRef 和 ByVal 参数
泛型方法参数
数组

在此抛出砖石,希望能引出大家的玉来。另外,已经有先人有了一些使用扩展方法的经验:
1. 当实例的类在某一个时间添加了一个方法,这个方法恰巧又与扩展的方法的特征一致,那么编译器会理所当然的选择类本身的方法来执行;
2. 不同的人有可能写出签名相同但功能、逻辑不同的扩展方法;
3. 把扩展方法放到独立的命名空间中,这样,调用者可以选择引入或者不引入命名空间以减少可能发生的冲突;
4. 扩展一个接口比扩展一个方法相对来得安全些,尤其是扩展不是自己的类/接口。因为写接口的人一般不会去改动接口,否则,实现接口的类必须全部作修改。但是,如果一个类继承自两个接口,这两个接口又很凑巧的有着相同签名的方法,那么,两个接口的扩展方法都会不可见。
5. 尽可能的扩展具体的类,换句话说,尽量不要去扩展基类。扩展基类的方法会被子类继承,显然,继承得层次越多,可能出现的冲突也就越多。

记不下来?呵呵,没有关系。什么时候出现有问题了,翻翻看看,也许许多麻烦就迎刃而解了。