VB 9.0中扩展方法特性,允许程序员在不修改、不继承现有类的前提下,给类添加自定义的方法。
在本文中,我们首先以Notebook类为例来看看如何使用扩展方法,然后,试着扩展一个系统现有的类,最后,参考一些巨人们关于扩展类使用的建议和注意点。有关Notebook类完整的代码,请大家参见VB 9.0新特性之局部类型推理(Local Type Inference)中的示例代码。
我们看到,Notebook类中,除去构造、释放资源相关的函数以外,剩下的方法就只有一个重写Object类的ToString()方法。相关代码如下:
Laptop Lenovo(T61) is 2.3kg, and at the price of 16000
现在,假设我需要把型号及价提取出来,中间加一个Tab,以供其它程序进行处理。这时,显然,比较合理的做法是给Notebook添加一个方法,例如叫GetPrintString() 来构造这样的字符串并返回。遇到这样的情况,扩展方法便有了其用武之地了。
首先,我们新建一个模块ExtNotebook。然后,引用System.Runtime.CompilerServices。最后,扩展一个GetPrintString方法。代码如下:
扩展方法写好了,然后,我们如何来调用它呢?我们来看一段调用扩展方法的代码实例:
第11行代码,我们调用了Notebook对象nb的GetPrintString()方法来返回字符串。其输出结果为:Lenovo〈tab> 16000。在引入了扩展方法的模块后,这个扩展方法调时跟类中其它方法一样,VS2008同样对其提供了Intellisence,只不过,方法前面的小图标中有一个向下的小箭头,表明,这是一个扩展方法,它的优先级不高。
![](https://www.cnblogs.com/images/cnblogs_com/xiaomi7732/ExtensionMethodIntellisence.png)
虽然这样子,但是,针对于这个例子,也许你会有一个疑问:为什么搞这么复杂,修改一下类,添一个方法不就了事了吗?嗯,那是因为这个例子中的类是自己写的。如果有一个类不是自己写的,但是要添加方法,恐怕你就没有办法修改类了。来看一个简单的例子。
比如,我们想要一个方法,把一个Integer对象的值直接输出到屏幕上,我们就可以针对Integer类扩展一个Print()方法。
以上我们进行的扩展方法,在调用时都不带有参数。下面,来试试带参数的形式。让我们对上述一例小做修改,在屏幕上输出Integer对象的值的N倍,N在调用时传入。
因此,我们看到,调用方法的参数总比扩展方法的参数少1个,因为扩展方法的第1个参数,用于确定扩展方法与要扩展的类之间的关系。
以上我们学会了如何对类进行方法的扩展,相信大家触类旁通,会想到对其它数据类型的扩展,那么,在VB 9.0里,到底有哪些数据结构可以进行方法的扩展呢?在此列出一个List:
在本文中,我们首先以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()方法时,它会把自己的品牌、重量、价格等属性构成一个字符串并予以输出。例如:![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
Laptop Lenovo(T61) is 2.3kg, and at the price of 16000
现在,假设我需要把型号及价提取出来,中间加一个Tab,以供其它程序进行处理。这时,显然,比较合理的做法是给Notebook添加一个方法,例如叫GetPrintString() 来构造这样的字符串并返回。遇到这样的情况,扩展方法便有了其用武之地了。
首先,我们新建一个模块ExtNotebook。然后,引用System.Runtime.CompilerServices。最后,扩展一个GetPrintString方法。代码如下:
1
Imports System.Runtime.CompilerServices
2
Module 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
8
End Module
扩展方法需要用<Extension()>标示,而Extendsion在System.Runtime.CompilerServices命名空间里,因此,在建立扩展模块后,我们做的第一步是引入命令空间;在<Extension()>标识后面,就跟普通方法一样写一个方法签名。其中,这个方法至少有一个不可省略的参数。这个参数在扩展方法中功能非同小可!它表明了扩展方法与类之间的关系。在这个示例中,因为这个参数notebook是Notebook类型的,所以,编译器就知道,要扩展的是Notebook类。所以,这个参数是什么类型,这个方法就会被扩展到对应类型的实例上。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
扩展方法写好了,然后,我们如何来调用它呢?我们来看一段调用扩展方法的代码实例:
1
Imports NoteBooks.ExtNotebook
2
Module Module1
3![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
Sub Main()
5
TryExtensionMehtod()
6
End Sub
7![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
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![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
End Module
15![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第1行代码,我们把扩展方法所在的模块引入;![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
15
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第11行代码,我们调用了Notebook对象nb的GetPrintString()方法来返回字符串。其输出结果为:Lenovo〈tab> 16000。在引入了扩展方法的模块后,这个扩展方法调时跟类中其它方法一样,VS2008同样对其提供了Intellisence,只不过,方法前面的小图标中有一个向下的小箭头,表明,这是一个扩展方法,它的优先级不高。
![](https://www.cnblogs.com/images/cnblogs_com/xiaomi7732/ExtensionMethodIntellisence.png)
虽然这样子,但是,针对于这个例子,也许你会有一个疑问:为什么搞这么复杂,修改一下类,添一个方法不就了事了吗?嗯,那是因为这个例子中的类是自己写的。如果有一个类不是自己写的,但是要添加方法,恐怕你就没有办法修改类了。来看一个简单的例子。
比如,我们想要一个方法,把一个Integer对象的值直接输出到屏幕上,我们就可以针对Integer类扩展一个Print()方法。
1
Imports System.Runtime.CompilerServices
2
Module ExtInteger
3
<Extension()> _
4
Public Sub Print(ByVal aInt As Integer)
5
Console.WriteLine(aInt.ToString())
6
End Sub
7
End Module
很显然,我们没有办法去修改Integer类,但是,通过样一个扩展,我们为Integer的实例添加了一个Print方法,用于将其输出到屏幕。调用方法也与其实例方法一致:![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
1
Imports NoteBooks.ExtInteger
2
Module Module1
3![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
Sub Main()
5
TryExtensionMehtod()
6
End Sub
7![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
Sub TryExtensionMehtod()
9
'
some other code
10
Dim nbPrice = 2000
11
nbPrice.Print()
12
'![](https://www.cnblogs.com/Images/dot.gif)
13![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
End Sub
15![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
16
End Module
17![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第10行,我们通过局部类型推理,得到了Integer的对象nbPrice,并且其值为2000,下一步,我们就调用扩展方法Print()将2000输出到屏幕上。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
15
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
16
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
17
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
以上我们进行的扩展方法,在调用时都不带有参数。下面,来试试带参数的形式。让我们对上述一例小做修改,在屏幕上输出Integer对象的值的N倍,N在调用时传入。
1
Imports System.Runtime.CompilerServices
2
Module ExtInteger
3
<Extension()> _
4
Public Sub Print(ByVal aInt As Integer)
5
Console.WriteLine(aInt.ToString())
6
End Sub
7![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
<Extension()> _
9
Public Sub Print(ByVal aInt As Integer, ByVal n As Integer)
10
Console.WriteLine(aInt * n)
11
End Sub
12
End Module
其中第9行有两个参数。第一个参数如前面所述,用于关联扩展方法要扩展的类,第二个参数则作为调用方法的第一个参数使用。看一下调用方法就清楚了。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
1
Imports NoteBooks.ExtInteger
2
Module Module1
3![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
Sub Main()
5
TryExtensionMehtod()
6
End Sub
7![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
Sub TryExtensionMehtod()
9
![](https://www.cnblogs.com/Images/dot.gif)
10
Dim nbPrice = 2000
11
nbPrice.Print()
12
nbPrice.Print(2)
13
![](https://www.cnblogs.com/Images/dot.gif)
14
End Sub
15![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
16
End Module
17![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
第12行调用了我们后来扩展的第二个方法。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
![](https://www.cnblogs.com/Images/dot.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
15
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
16
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
17
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
因此,我们看到,调用方法的参数总比扩展方法的参数少1个,因为扩展方法的第1个参数,用于确定扩展方法与要扩展的类之间的关系。
以上我们学会了如何对类进行方法的扩展,相信大家触类旁通,会想到对其它数据类型的扩展,那么,在VB 9.0里,到底有哪些数据结构可以进行方法的扩展呢?在此列出一个List:
类(引用类型)
结构(值类型)
接口
委托
ByRef 和 ByVal 参数
泛型方法参数
数组
在此抛出砖石,希望能引出大家的玉来。另外,已经有先人有了一些使用扩展方法的经验:
1. 当实例的类在某一个时间添加了一个方法,这个方法恰巧又与扩展的方法的特征一致,那么编译器会理所当然的选择类本身的方法来执行;
2. 不同的人有可能写出签名相同但功能、逻辑不同的扩展方法;
3. 把扩展方法放到独立的命名空间中,这样,调用者可以选择引入或者不引入命名空间以减少可能发生的冲突;
4. 扩展一个接口比扩展一个方法相对来得安全些,尤其是扩展不是自己的类/接口。因为写接口的人一般不会去改动接口,否则,实现接口的类必须全部作修改。但是,如果一个类继承自两个接口,这两个接口又很凑巧的有着相同签名的方法,那么,两个接口的扩展方法都会不可见。
5. 尽可能的扩展具体的类,换句话说,尽量不要去扩展基类。扩展基类的方法会被子类继承,显然,继承得层次越多,可能出现的冲突也就越多。
记不下来?呵呵,没有关系。什么时候出现有问题了,翻翻看看,也许许多麻烦就迎刃而解了。
Little knowledge is dangerous.