Friday, August 13, 2004

 

Visual Basic 2005新功能点评

作者:ninputer
出处:http://blog.joycode.com/ninputer/

(一)——带有不同可见性级别访问器的属性

从本篇起,我将陆续介绍Visual Basic 2005新增加的语言功能和IDE功能。但是Visual Basic 2005现在还处于研制的早期,其功能还有很多没有确定,因此本文的内容可能与最终发布版本不符。

Visual Basic 2005有很多值得一看的功能,比如泛型、运算符重载和最受关注的My命名空间等,我打算把有趣的放在后面,第一篇我将介绍带有不同级别访问器的属性。

在Visual Basic 4.0、5.0和6.0中,属性过程要为读取、写入书写不同的过程,如我想实现一个可读写的属性,至少要写两个过程:

Public Property Get MyProp() As Integer
Public Property Let MyProp(ByVal value As Integer)

其最大的优点是可以让掌管读取属性值的过程和掌管写入属性值的过程具有不同的可见性级别,如

Public Property Get MyProp() As Integer
Friend Property Let MyProp(ByVal value As Integer)

这么写的意思是,在该项目内,该属性是可读写的,而在项目外,该属性就是只读的。这个功能在组件设计中特别有用。

到了Visual Basic .NET中,属性的语法被改成在统一的Property过程中书写Set访问器和Get访问器。我们只能让属性的Set和Get有相同的访问级别。这样一来,虽然语法更加紧凑,却丢失了原先Property过程能在不同级别显出不同可见性的功能,这在VB.NET推出的早期是被反复提及的缺点之一。因此Visual Basic 2005决定将这一功能重新加入Visual Basic,现在属性过程的Get和Set访问器可以有不同的访问级别,其语法如下:

Public Property MyProp As Integer
Get
Retrun m_MyProp
End Get
Friend Set(ByVal value As Integer)
m_MyProp = value
End Set
End Property

与早期版本不同,这种语法要求Property语句前边有一个可见性级别的修饰符,而在需要有与这个修饰符不同访问级别的访问器Get或Set前面加以另一种修饰符。不能在Get和Set两者前面同时增加访问级别修饰符,也不能在ReadOnly或WriteOnly属性的访问器前面加修饰符。要注意的是,Set或Get访问器的可见性必须比Property前面修饰符的可见性更严格(即只能缩小原来的可见性),否则是非法的。

上面这种Public Get - Friend Set模式的属性最为常用,而Visual Basic 2005有更多的访问级别可用,创建出在更多有用的属性。如

Protected Friend Property MyProp As Byte
Protected Get
End Get
Set(ByVal value As Byte)
End Set
End Property

在该程序集的非子类中,这个属性是只写的,而在程序集外部的非子类中,这个属性又变为不可访问的,只能从子类访问这个属性。还有很多种搭配模式,比如Protected Friend Get - Protected Set这种模式,都是希望改变组件内外互相访问权限的常用手段。

结论:带有不同访问级别访问器的属性给我们设计组件带来了更多的灵活性,它可以让属性再不同的级别显示出不同的访问性。最后提到一点,C# 2.0也支持这个功能,而且语法十分相似。

(二)——有用的小语法

Visual Basic 2005除了增加令人振奋的新功能以外,还增加了很多实用的小语法。比如本文要介绍的Continue语句、Using语句、Clear语句、数组定义的新语法、Global关键字等。

一、Continue语句

以前Visual Basic语法的一个重要缺陷是无法提前结束单前一轮循环,直接进入下一轮循环。如果需要此功能,只能用GoTo语句。现在,Visual Basic 2005增加了Continue语句来实现此功能。Continue必须紧接For、Do或While语句,表示提前进入该循环的下一轮:

For i As Integer = 1 To 100
'Some codes
Continue For
'Some other codes
Next

和Exit语句一样,Continue语句可以跨越不同的循环执行,如:

For i As Integer = -10 To 10
Do While j <= GetInteger(i)
'codes
If i > 0 Then Continue For
'codes
Loop
Next

二、Using语句

在C#里有一种using语句,在using语句中初始化的变量可以自动调用Dispose方法而被处置。using语句的实质是一个try...finally结构。现在Visual Basic 2005也具有这个语法:

Using r As New StreamReader(”c:\mytext.txt”)
s = r.ReadToEnd()
End Using

当语句跳出Using块时,r.Dispose()将自动被调用,确保非托管资源被顺利释放。使用Using语句要注意,Using中的变量必需实现IDisposable接口,而且要在进入Using前正确初始化。

三、数组的新语法

以前VB的数组支持下界从指定的数值开始,而VB.NET则要求数组下界必须从0开始。这个改变让很多开发者感到别扭。Visual Basic 2005增加了一个新的语法,让有指定下界习惯的开发者“感觉好一点”:

Dim a(0 To 100) As Integer

其实,还是只能从0开始,但是现在可读性更高了,一眼就可以看出数组的下界和上界。

还有一个传统语法回到了Visual Basic 2005中,那就是Clear语句。如:

Clear a

此语句的功能是将a的所有元素置为初始值(在VB中即是Nothing)。这个语句的功能就等于用Redim按原来的大小重新定义数组。

四、Global关键字

在Visual Basic .NET中,命名空间的判定上有一个问题,见下列代码:

Namespace MySpace
Namespace System
Public Module EntryPoint
Public Sub Main()
Dim r As Double
r = System.Math.Sin(1.345)
End Sub
End Module
End Namespace
End Namespace

这段代码能正确编译吗?答案是不能,它会提示Math不在System命名空间中。为了解决这个问题,Visual Basic 2005提供了Global关键字,永远代表根命名空间。如上面那段代码中的黑体部分改成:

r = Global.System.Math.Sin(1.345)

就可以正确执行了。

(三)——代码建议、重构支持

Visual Basic 2005不仅在语法方面有许多新增功能,IDE上也有很大改进。

Visual Basic从很早就具有后台编译的功能,在书写代码的同时就能获得编译错误的提示。Visual Basic 2005在此基础上增加了代码建议功能。当输入错误的代码时Visual Basic 2005的IDE将会用智能标记的方式给出更正的建议,这大大提高了编程效率。比如,当输入错误的类型时,IDE将会用下拉列表建议改正方案。

上图建议将错误的Intager更正成Integer或UInteger。另一个例子是,如果将关键字错误地当作标志符,IDE会建议加入[]来屏蔽对关键字的检查。

另外还有一些问题,比如含有MustOverride过程的类型必须是MustInherit的,但是许多开发者通常对以往的IDE提示不知所措。现在,这类问题可以通过代码建议轻松解决。

与此类似,很多代码问题都有相应的代码建议,这下可解决了开发者的大问题。

重构是改善既有代码的途径,如果没有重构工具,进行重构是十分麻烦的。VC# 2005将具有完善的重构支持,是通过“重构”菜单中的选项手工进行的。与此相比,Visual Basic的重构是默默进行的。比如你要将字段a重新命名为a1,立刻就会弹出Rename选项。

当你选择“Rename 'a' to 'a1'”时,不仅定义修改了,整篇代码中所有用到字段a的地方都会比自动重名名为a1,而与此无关的a字母是不会被更改的。Visual Basic的开发组解释说Visual Basic 2005不会加入“重构”菜单,因为用户不懂那是什么意思的话可能永远也不会选它。但是这种人性化的智能标记就不同了,无论初学者还是老手,都能快速得到帮助,完成优良的代码。

(四)——不完全类型

不完全类型(Partial Types)的概念首先源于将同一个类分布在多个文件中完成的需求。以前,类或结构必须在同一个文件中完成,当类型变得很大,或许多开发人员需要同时开发一个类型时,这种一个文件的缺点就越来越明显了。Visual Basic 2005允许在多个文件中储存一个类型的“Partial”,这些不完全类型能在编译时组成一个完整的类型。这种技术还可以用于分离IDE自动生成的代码及用户书写的代码,以及孙展波提到的,在ASP.NET和Avalon中分离工具生成的代码及用户书写的代码。

Visual Basic 2005的不完全类型的语法与C#不太一样,其主要特点是:

1、Partial是关键字。C#受限于ECMA的标准,不能任意添加关键字,因此像partial或yield等均不是关键字。而Visual Basic不受标准限制,Partial是作为关键字出现的,不能用作标识符。

2、只有类或结构可以有Partial类型,模块是不能Partial的。

3、一个类的所有Partial类型的定义中,有一个可以不加Partial关键字。当使用Partial关键字时,IDE会自动提示扩写哪个类型:



4、在各个Partial类型上定义的修饰符、Attribute、继承和实现接口是叠加关系,比如一个MustInherit Class A和一个Partial Friend Class A在一起,就会形成一个Friend MustInherit Class A。当然这些修饰不能互相冲突,一个是Public而另一个是Private可不行。

5、在一个Partial类型上声明实现的接口可以在另外一个Partial中实现,一个Partial上声明继承的类的成员也可以在其他Partial中重写。任何时候在任何一个Partial类型中,感觉都同一个完整的类型内部没有区别。

6、Partial类型中定义的嵌套类型也可以写成Partial类型。

下面的例子展示了Partial Type的多种用法

Friend Class Class2
Inherits Class1

'MustOverride需要类型定义为MustInherit
'该修饰符在另外一个Partial定义上
Public MustOverride Sub Test()

Public Function Clone() As Object _
Implements System.ICloneable.Clone

'实现另外一个Partial中书写的接口
End Function


Partial Class IntClass '内部定义的嵌套类也可以Partial

End Class
End Class

'另一个文件中
Partial MustInherit Class Class2
Implements ICloneable

Public Overrides Sub Test1()
'重写另外一个Partial中继承类的方法
End Sub

Public Class IntClass '内部定义的嵌套类也可以Partial

End Class
End Class

Visual Basic 2005和Visual C# 2005现在都采用不完全类型来储存IDE生成的窗体代码。将自动生成的代码放到一个FormXX.Designer.vb中,这样窗体类就比以前采用Region折叠的方式更加简洁,同时更能够防止用户无意中修改自动生成的代码。

(五)——自定义事件过程

在Visual Basic等语言中,事件是经过特别封装的Delegate变量,它可以确保事件机制的进行,却又不允许从对象的外部引发事件或者获取不是自己绑定的事件处理的信息。所有这些,都是通过“事件过程”来做到的。当你书写一个事件的定义时:

Public Event MyEvent As EventHandler

实际上是书写了一个私有的委托变量和两个公共方法——add_MyEvent方法和remove_MyEvent方法。

Private _MyEvent As EventHandler
Public Sub add_MyEvent(value As EventHandler)
_MyEvent = CType(Delegate.Combine(_MyEvent, value), _
EventHandler)
End Sub
Public Sub remove_MyEvent(value As EventHandler)
_MyEvent = CType(Delegate.Remove(_MyEvent, value), _
EventHandler)
End Sub

当你调用下列语法为事件绑定处理过程时:

AddHandler obj.MyEvent, AddressOf MyProc

实际是调用了obj.add_MyEvent方法:

obj.add_MyEvent(AddressOf MyProc)

通过这种方法,私有的委托变量被完全封装起来,因此从对象外部直接访问它是不可能的。这种自动的事件过程为我们提供了很大的方便,但不能满足一些高级的要求。比如我们想维护一个自定义的委托列表,供所有子类的事件使用,就没有办法使用自动生成的事件过程,而必须要用新的自定义事件过程。自定义事件过程就是手工实现事件的add_EventName方法和remove_EventName方法。其语法是

modifiers Custom Event EventName As HandlerName
AddHandler(value As HandlerName)
statements
End AddHandler

RemoveHandler(value As HandlerName)
statements
End RemoveHandler

RaiseEvent(arglist)
statements
End RaiseEvent
End Event

书写自定义的事件过程,必须实现AddHandler、RemoveHandler和RaiseEvent三个访问器。其中AddHandler、RemoveHandler访问器的内容将成为add_EventName()方法和remove_EventName()方法的内容,而RaiseEvent访问器则是成为一个私有方法,用于引发事件。RaiseEvent访问器的参数必须是事件委托类型所定义的参数,其一般内容为判断委托类型是否为Nothing以及执行委托。使用自定义的事件过程将不会产生默认的委托变量,这一部分完全由用户来完成。下面用一个例子展示如何将自定义的委托变量封装成事件:

Protected MyDel As EventHandler '自定义的委托变量

Public Custom Event MyEvent As EventHandler

AddHandler(value As EventHandler)
'为自定义委托绑定处理过程
MyDel = CType(Delegate.Combine( _
MyDel, value), EventHandler)
End AddHandler

RemoveHandler(value As EventHandler)
'解除自定义委托的处理过程
MyDel = CType(Delegate.Remove( _
MyDel, value), EventHandler)
End RemoveHandler

RaiseEvent(sender As Object, e As EventArgs)
'引发事件
If MyDel IsNot Nothing Then
MyDel(sender, e)
End If
End RaiseEvent

End Event

以上例子只是重复了默认事件过程,实际上,自定义事件过程可以增加任何代码,在绑定事件和解除绑定时执行任何自定义内容,比如计算绑定计数,计算引发次数的计数等等。通过自定义事件过程,我们可以创建出更加灵活的事件。

(六)——运算符重载 part.1

关键词:VB2005 VB8.0 VB.NET Whidbey

从本篇起,我会花几篇贴子讨论这个Visual Basic 2005中引入的重大功能——运算符重载。运算符重载是一项很有趣的功能,使用它可以给自己的类型定义运算符。比如我们常常遇到两个对象“相加”的概念,比如两个矩阵的相加,两个集合相加等等,如果没有运算符重载,我们是用对象的方法来执行这类操作的。比如A和B是具有可相加概念的两个对象,那么传统的方法是提供一个“Add”方法:

A.Add(B)
'或者用A、B所属类型T的一个静态方法
T.Add(A, B)

这样的操作当需要频繁使用各种运算时就会变得复杂和难以理解,比如A + B - C + D就得表示成

A.Add(B).Subtract(C).Add(D)
'或者用A、B、C和D所属类型T的静态方法表示
T.Add(T.Subtract(T.Add(A, B), C), D)

无论那种写法都非常的难看而且冗长。如果我们可以直接使用+或-这样的运算符多好。现在,运算符重载使这个愿望成真了。

Visual Basic 2005中运算符重载的本质是类的一组公共静态方法,每个可以重载运算符都有一个名称,比如二元运算符+号的名称是op_Addition。要注意的是,运算符重载这一特征是不符合公共语言规范(CLS)的,因为大部分语言不支持运算符重载。即使支持运算符重载的主流.NET语言(如C#、Delphi)可重载的运算符与VB支持重载的运算符也不完全相同,因此重载运算符会带来跨语言的障碍。另一个要注意的问题是,重载后运算符的意义应当和运算符原来的意义相似,这也是尽量减少跨语言障碍的规则之一,比如VB和C#的移位运算符都是<< 和 >>,而Delphi的则是Shl和Shr,如果重载移位运算符用做向流中添加对象(就像C++那样),那么在Delphi中使用该运算符的语法将变得不可理解。以下表格对比了Visual Basic和C#各自支持重载的运算符:

Visual Basic C# 运算符的名称
+(一元) +(一元) op_UnaryPlus
-(一元) -(一元) op_UnaryNegation
IsTrue true op_True
IsFalse false op_False
(无此运算符) ! op_LogicalNot
Not ~ op_OnesComplement
(无此运算符) ++ op_Increment
(无此运算符) -- op_Decrement
+ + op_Addition
- - op_Subtraction
* * op_Multiply
/ / op_Division
\ (无此运算符) op_IntegerDivision
& (无此运算符) op_Concatenate
^ (无此运算符) op_Exponent
<< << op_LeftShift
>> >> op_RightShift
=(判断等值而不是赋值) == op_Equality
<> != op_Inequality
< < op_LessThan
<= <= op_LessThanOrEqual
> > op_GreaterThan
>= >= op_GreaterThanOrEqual
And & op_BitwiseAnd
Or | op_BitwiseOr
Xor ^ op_ExclusiveOr
Mod % op_Modulus
Like (无此运算符) op_Like
CType (用户定义的类型转换) op_Explicit和op_Implicit

从这个表可以清楚地看出Visual Basic所支持重载的所有运算符以及Visual Basic与C#支持的运算符中不兼容的部分。

本篇首先介绍普通二元运算符,包括+、-、*、/、\、Mod、&、^、And、Or、Xor、<<、>>和Like。这些运算符重载的语法是:

Public Shared Operator OpSymbol(LeftArg, RightArg)As ReturnType

其中LeftArg和RightArg中至少有一个必须是定义运算符的类型,否则将可能会重载定义在别的类型上的运算符。这种普通运算符对返回类型一般没有要求,但建议根据运算符的意义而定。比如&运算符通常返回字符串而Like运算符通常返回布尔型变量。下面的例子展示了重载一个向量结构的+运算符,以便进行两个向量和的运算:

Public Structure MathVector
Public x, y, z As Double

Public Sub New(initX As Double, initY As Double, initZ As Double)
x = initX
y = initY
z = initZ
End Sub

Public Shared Operator +(left As MathVector, right As MathVector) _
As MathVector

Return New MathVector(left.x + right.x, _
left.y + right.y, _
left.z + right.z)
End Operator
End Structure

这些运算中的+、-、*、/、\、<<、>>、^等运算符隐含了对赋值运算符的重载,比如重载了+就相当于重载了+=运算符,但是不能直接重载赋值运算符。普通二元运算符重载非常容易,也最为常用。下次我们讨论一元运算符和比较运算符。

(七)——运算符重载 part.2

接上此,我们继续讨论运算符重载。本次首先讨论一元运算符。Visual Basic允许重载的一元运算符包括+、-、Not、IsTrue和IsFalse。其中IsTrue和IsFalse运算符因其特殊性放在后文比较运算符中介绍。一元运算符重载的语法是:

Public Shared Operator OpSymbol(Arg)As ReturnType

其中运算符参数Arg必须是定义运算符的类型,而+、-和Not的返回值没有要求。下面例子演示求复数相反数(用-)和共轭复数(用Not)的例子:

Public Shared Operator -(val As Complex)As Complex
Return New Compex(-val.Real, -val.Imaginary)
End Operator

Public Shared Operator Not(val As Complex)As Complex
Return New Complex(val.Real, -val.Imaginary)
End Operator

比较运算符包括=、<>、>、>=、<和<=,由于IsTrue和IsFalse与比较运算符密切相关,因此也放在一起讨论。比较运算符是二元运算符,它与普通二元运算符相比有以下限制(包括IsTrue和IsFalse):

1、他们必须成对重载,即重载了=就必须重载<>,而重载了IsTrue就必须重载IsFalse。

2、它们的返回值必须是Boolean型。

对于=和<>运算符,重载他们就要求重写Object.Equals和Object.GetHashCode两个方法,因为若不重写他们,将造成语义不统一的现象。如果类型重载了比较大小的运算符,也最好实现IComparable接口,以便提供一致的比较方案。

IsTrue和IsFalse运算符无法在代码中使用,他们也不是VB的关键字。但Visual Basic在处理AndAlso和OrElse运算符时会用到它们。AndAlso和OrElse是不能重载的逻辑运算符,但如果重载了And、Or和IsTrue、IsFalse运算符就可以使用它们,VB将按照如下规则计算X AndAlse Y:

IIf(IsFalse(X), X, X And Y)

而按照如下规则计算X OrElse Y:

IIf(IsTrue(X), X, X Or Y)

这里我们只取IIf的语义,即把它想象一个内联函数,而实际不是调用缓慢的IIf函数。IsTrue和IsFalse还用于隐式将类型转换为Boolean型,它可以使类型表示既不是True也不是False的Nothing。同时,重载了IsTrue和IsFalse运算符的类型还能用于If、While、Do和For等需要布尔变量的语言环境中。

下次我们介绍类型转换运算符和符合CLS的设计指南。

(八)——运算符重载 part.3

关键字 VB2005 VB.NET Whidbey

本贴是运算符重载的最后一期,首先介绍重载类型转换运算符CType。首先我们复习一下CType与VB其它类型转换运算符的功能:

CType用于将一种类型转换为另一种类型,这种转换可以是装箱、拆箱、接口转换、父类转换、用户定义的转换等。有一些转换,如从String到Integer的转换是.NET不支持的,但是VB内建了转换的规则。

DirectCast只能将类型转换为其运行时的真实类型,比如从装箱的Object类型还原为值类型。DirectCast在进行拆箱操作时比CType更快。不能重载此运算符。

TryCast运算符是VB2005新增,它结合了DirectCast和类型判断的功能。如果类型转换能够成功,它将返回转换的结果。而如果不能成功,则返回Nothing,不会产生异常。TryCast也不能重载,而且它不能用于值类型的转换。用过C#中as运算符的人应该可以很快理解TryCast的功能。

我们重载CType一般是用于定义两种不同的类型之间的转换,比如Integer到Decimal的转换等。在重载它时,我们必须明确转换是“扩大转换(Widening)”还是“缩小转换(Narrowing)”。扩大转换的意思是转换的源类型A的一切可能值或实例都能转换成目标类型B的值或实例。缩小转换则是指A中存在不能成功转换为B的值或实例。如Integer的所有值都能转换为Long,因此Int32到Int64的转换是Widening的;而Decimal中不是所有值都能转换成Long,Decimal可以表示Long无法表示的超大数值,他们将导致转换失败,因此从Decimal到Int64的转换就为Narrowing的。

真实中常见的类型转换大都是Narrowing的,因此重载CType时常进行Narrowing重载。只有确信需要时才采用Widening重载,而且如果定义了A到B的Widening转换运算符重载,那么从B到A的转换就不应该是Widening的,否则在同时需要A、B类型作参数的场合就无法分辨二者的实例。下面我们看CType运算符重载的语法

Public Shared {Narrowing|Widening} Operator CType _
(srcVar As SourceType) As TargetType

正如我们前面介绍的,重载CType时必须指定这种类型转换为Narrowing还是Widening。同时SourceType和TargetType中有一个必须是定义此运算符的类型。书写重载过程的主要任务是根据srcVar变量的内容生成一个新的TargetType的变量。下面我们演示定义复数于实数类型的转换运算符。我们知道只有复数的虚部为0时才能转换为实数,因此复数到实数的转换为Narrowing,而所有实数都能转换为复数,因此实数到复数的转换为Widening。

Public Shared Narrowing Operator CType(c As Complex)As Double
If c.ImaginaryPart <> 0 Then
Throw New InvalidCastException( _
"无法将虚部不为零的复数转化为实数"
Else
Return c.RealPart
End If
End Operator

Public Shared Widening Operator CType(r As Double) As Complex
Return New Complex(r, 0)
End Operator

我们可以看到,在Narrowing转换中必要的内容是对无法转换的情况抛出异常,而Widening转换应确保所用的语句不会产生异常。在VB.NET中,无论是Widening转换还是Narrowing转换,通常都可以省略CType运算符不写(在其他语言中称隐式转换),不过省略Narrowing转换的CType会给出警告,而且在Option Strict打开的情况下无法通过编译。比如下面的代码可以通过编译并正确运行,虽然Int32到Int16的转换是Narrowing的:

Dim s As Short = 10I

但是若将Option Strict打开,这段代码就无法编译,因为从Integer到Short的转换不是总能成功的,而这样的写法不能给开发者带来这一认识。

最后我们来了解一下符合CLS设计的原则:

1、每个运算符重载都应该提供一个相同功能的,符合CLS的方法。比如+运算符提供Add方法,CType运算符提供ToType方法等等。

2、在结构中使用运算符重载,而在类中慎用。一般重载=和<>这样的运算符比较常用,而特殊的运算符,如&、^或Like不要随意重载。

3、注意各种语言对不同运算符的支持,可以查看我的对照表来确定。

(九)——插入代码片断

Visual Basic 2005吸引人的地方之一就是它大幅增加了开发效率,其中一大体现就是本期要介绍的插入代码片断。代码片断大家都很熟悉,就是一些常见短小的代码。我们在开中常常遇到一些反复使用的代码片断,比如Try...Catch块、操作字符串和访问注册表的代码等等。每次输入这些代码十分麻烦,因为每次使用大部分都很相似,只有少数地方有变化,却要照单输入。Visual Basic 2005为我们增加了“插入代码片断”的功能,可以用鼠标轻轻一点,就插入我们所需的代码片断。Visual Basic 2005已经内置了大量的片断,因此遇到我们不熟悉的任务时,可以先看看有什么现成片断可以用,实在是方便极了!

现在我们插入一个片断试试看,比如“求两个日期的间隔”,这曾经是个网络上相当普遍的问题。插入片断后显示如下的代码:

' Click for more: 'ms-help://MS.VSCC.v80/MS.MSDNQTR.80.en/commoner/redir/redirect.htm?keyword="688f80b8-002b-49eb-833e-738041b81508"'
Dim oldDate As Date = #1/1/2002#
Dim newDate As Date = Now
Dim differenceInDays As Long = DateDiff(DateInterval.Day, oldDate, newDate)
Dim spanFromDays As TimeSpan = New TimeSpan(CInt(differenceInDays), 0, 0, 0)

这里面有几个相当有趣的地方,首先那条注释中包含了关于此片断帮助的链接,如果对此片断的功能有疑问,可以直接通过链接查看帮助(多么体贴的设计)。注意代码中有两处黄色区域,这些是可替换代码的区域。单击Tab键,光标将自动在所有可替换区域之间跳转。如此可以快速地将这些区域替换成自己的内容。这些替换区域可以长时间保持可替换状态。

在过程内部可以插入的片断与在类/结构的声明区域或命名空间中可插入的片断是不一样的。比如下面这个片断只能在类的声明区域插入,其作用是响应系统桌面变化的信息:

' Click for more: 'ms-help://MS.VSCC.v80/MS.MSDNQTR.80.en/commoner/redir/redirect.htm?keyword="2e578cf5-b675-4ffe-a91f-17be0015df36"'
Private Sub HookUpEvent()
AddHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, _
AddressOf UserPreferenceChangedSub
End Sub

Private Sub UserPreferenceChangedSub(ByVal sender As System.Object, _
ByVal e As Microsoft.Win32.UserPreferenceChangedEventArgs)
If (e.Category = Microsoft.Win32.UserPreferenceCategory.Desktop) Then
MsgBox("Desktop changed."
End If
End Sub

这段代码的功能一般的VB程序员很难写出,如果没有插入片断,不知道要费多少功夫了。除了内置的片断以外,我们还常常需要插入自己想要的代码。比如对于我来说,我常常需要书写一个将私有字段封装为属性的代码片断。对于这种需求,Visual Basic 2005还提供了“创建代码片断”的功能。如图,现选中想要制作成片断的代码,再从上下文菜单中选择“Create Snippet”功能。

接下来,我们可以通过片段设计器设置片段中的可替换区域,所属类别等信息。

所有可插入的片断在实质上都是一小段XML代码。这项功能在当前版本的Visual Studio 2005预览版本中还没有完成,我们期待在正式版本中更佳表现。

(十)——异常助手

在使用Visual Basic .NET开发的过程中,我们都有以下经验,那就是VB.NET在编译错误方面给出的帮助很详细,可以很方便地找到解决问题的方法,但运行时错误就差多了,比如我们书写了下面的代码:

Dim b As Button
b.Text = "Hello"

当代码运行到此处时,只会弹出下面这样一个窗体。

一句“未将对象引用设置到对象实例”难倒了多少人。对于初学者,他们根本不知道这意味着什么,更不要提解决问题了。不要指望那个“帮助”按钮有什么帮助,他只是解释一下什么叫异常窗口,声么叫“中断”等等毫无关系的内容。这样的异常窗口对开发着的帮助实在非常有限,他甚至不能链接到关于异常类的文档。这样的异常窗口甚至不如VB6提供的信息多,这导致更多VB6程序员在升级到VB.NET时遇到阻碍。Visual Basic 2005以及在Visual Studio 2005中的C#和J#语言新增了异常助手功能,很大程度上解决了这个问题。如果我们在VB2005中输入上述代码,运行时将得到如下一个窗口。这就是异常助手,他分为三个部分:首先像VB.NET一样提供了异常对象所附带的信息,再有就是两个新的部分:Troubleshooting Tips和Actions。Troubleshooting Tips是关于异常的帮助,不仅仅是异常类的文档,还有特定语言如何解决问题的指南。比如NullReferenceException就提供了一篇Troubleshooting NullReferenceException Exceptions ,其内容摘录如下:

Use the New keyword to create the instance. You may have tried to use an object without providing an instance of the object. For example, Dim CustomerTable As DataTable should be rewritten as Dim CustomerTable As New DataTable.

Include a code block that checks for null references. Programmatically check to determine if a function has returned null (Nothingin Visual Basic) instead of an instance of an object.

Explicitly catch NullReferenceException in a Try…Catch…Finally statement. A Try…Catch…Finally statement can check for specific types of exceptions, going from most specific to least specific.

比起VB.NET毫无帮助的信息,这条信息已经人性化多了,最起码它能建议进行空引用的检查、使用New关键字和使用Try语句等常用的手段。

异常助手对于开发者的帮助是很大的,比如安全性导致的异常通常都十分棘手,因为许多不同的代码都可以导致同一条安全性异常,这样即使查阅异常相关的文档也难以发现问题所在。而异常助手给出了非常详细的帮助,在Troubleshooting提示部分给出了大量链接。

Action部分是可以自动执行的任务,目前的版本,Action主要的功能是启动异常细节窗口,这比使用狭窄的“本地”窗口来查看异常对象的信息要舒适多了。异常助手给开发者带来了巨大的方便,用好这项功能,能使开发效率进一步的提升。异常助手如果结合“编辑后继续运行”来使用,将焕发难以执行的效率,我们下次就介绍“编辑后继续运行”。

(十一)——编辑后继续运行

我们都有调试程序的经验,当程序发生了异常或遇到断点中断执行时,如果我们发现代码中有问题需要修改,一般的开发工具需要完全重新编译后再次运行到此处才能检验是否修改正确。有时候项目重新运行到所需位置的速度很慢或者操作非常复杂时,完全编译再调试的执行方式就显得十分笨拙和低效。在Visual Basic还能够解释执行的时代,Visual Basic程序员都有一项非常便利的调试手段,那就是编辑后继续运行。我们可以在程序中断的情况下修改代码,然后立即从此处继续运行,有时甚至可以跳过出现异常的语句进行下去。这样,即使我们要反复修改代码并运行,也可以完全在调试时进行,这是多么方便。不过Visual Basic .NET没有这一功能,这让很多Visual Basic程序员十分扫兴,因为这是VB开发中的巨大优势之一。Visual Basic 2005重新加入了这一功能,让广大Visual Basic程序员又可以享受编辑后继续运行带来的便利。这一特性是在CLR层次上实现的,因此理论上任何建立在CLR上的语言都可以实现编辑后继续运行,但是Visual Basic在所有语言中最适合拥有这个特征,因为Visual Basic在书写代码时就立即编译,只有这样,中断时作出的修改才有可能立刻得以运行。在Visual Studio 2005中,目前也确实只有Visual Basic 2005带有这个功能。

演练

首先我们看看编辑后继续运行是如何工作的。这段代码将产生被零除的异常,我们用它来展示编辑后继续运行的功能:

Dim i, j As Integer
i = 1
j = 0
MsgBox(i \ j)

运行这段代码,我们将得到一个运行时异常。

根据异常助手的指示,我们发现问题在于j在运行时被赋予0,而在进行除法时没有对j的值进行验证。这时,关闭异常助手,便可以立刻修改代码。比如我们修改成

If j <> 0 Then MsgBox(i \ j)

我们注意到编辑器左边有个黄色的箭头。那个箭头叫做instruction pointer,指示是将要执行的代码。

现在我们就可以单击“运行”按钮继续运行了,正如预期的那样,异常不再发生。在中断状态,我们甚至可以手工控制运行的流程,而这个操作简单到只需动动鼠标。举个例子,假设刚才的错误发生时我们希望更改对j的赋值语句,然后运行看效果如何,那么我们可以在刚才中断下来的时候,将代码改为:

Dim i, j As Integer
i = 1
j = 1
MsgBox(i \ j)

这时黄色的箭头指向MsgBox语句,如果直接继续运行还是错误的结果。于是我们可以拖动黄色的箭头,使它指向j的赋值语句。

接下来单击“运行”按钮,立刻就能看到弹出的消息框显示了正确的结果。

限制

Visual Basic 2005的编辑后继续运行功能是有一些限制的,比如将断点处的语句用With或SyncLock语句包围、移除局部变量的定义、改变循环语句或者改变调用堆栈中某方法的调用语句等等都不能继续运行。此外所有使用了泛型的语句修改后大都不能继续运行。这些限制是由CLR内部的工作原理引起的。关于具体的限制,MSDN的文档将会有详细的描述。

即使存在这些限制,我们的编辑后继续运行功能已经非常强大了,使用编辑后继续运行可以大大提高调试的效率,节约时间,而且对于初学者来说更易于学习和接受。

(十二)——无符号整型

BASIC语言的类型系统在其发展过程中是不断完善的。实用派的Microsoft BASIC系列首先给Visual Basic增加了整型类型,VB又增加了Date型和Currency型;;VB4中加入了Boolean、Object和Byte类型;VB5加入了Decimal类型;VB.NET又加入了Char类型和更大的整型。VB.NET的类型系统依靠.NET Framework类型已经非常完善。但是在Visual Basic的发展史上从没有支持过无符号整数,而C系列的语言则对他们有良好的支持。因此Visual Basic在与C/C++编写的代码进行交互时总是会遇到障碍。.NET Framework支持无符号整型,但是他们不符合CLS,Visual Basic .NET也没有支持他们。事实上,无符号整型用处也不是很大,只是在进行平台交互的时候常会用到他们。不管怎么说,Visual Basic 2005现在支持他们,你可以用他们进行计算,就像其他类型一样。

Visual Basic 2005支持的无符号整型和有符号的SByte类型都是对.NET Framework中现有支持的无符号整型结构体的映射。下表列出了这些新类型的取值范围和映射的结构体:

Visual Basic中的名称 字面符号 取值范围 映射的结构
UShort US 0~65535 System.UInt16
UInteger UI 0~4294967295 System.UInt32
ULong UL 0~18446744073709551615 System.UInt64
SByte 无 -128~127 System.SByte


字面符号是用来表示这些类型字面常量的,比如100可以表示有符号整数,也可以表示无符号,但是100UI就只能表示UInteger的100。对无符号整数的运算超出他们的取值范围默认会发生溢出异常,而将无符号整数转换为相应大小的有符号整数时,若所表示的数超过相应的有符号整数所能表示的范围,也会发生溢出异常,使用时应当注意。

Visual Basic 2005还引入了几个新的类型转换运算符,用于其他类型到无符号类型的强制类型转换。他们是CUShort,CUInt,CULng和CSByte。他们的用法与其它类型转换运算符是一样的。如:

Dim ui As UInteger = 100UI
Dim us As UShort = CUShort(ui) '显式转换
Dim ul As ULong = ui '隐式转换

注意无符号整型到同样大小的有符号整型不是扩大转换,而是缩小转换,在Option Strict完全打开的状态下只能进行显式转换。

Visual Basic 2005还支持创建基于无符号整型的枚举类型,只要在枚举定义的时候指定即可。比如指定基于ULong类型的枚举:

Public Enum MyEnum As ULong
A
B
C
End Enum

这时,MyEnum类型就和ULong类型一样,他的变量可以取任何ULong能取的数值。而A、B和C变量也都是实质为ULong类型的常数。

无符号整型最常用在与平台进行交互的地方,比如COM交互或调用平台的API。比如调用Win32的API函数:

Public Class windowsMessage
Private Declare Auto Function mb Lib "user32.dll" Alias "MessageBox" _
(ByVal hWnd As Integer, _
ByVal lpText As String, _
ByVal lpCaption As String, _
ByVal uType As UInteger) As Integer

Private Const MB_OK As UInteger = 0
Private Const MB_ICONEXCLAMATION As UInteger = &H30
Private Const IDOK As UInteger = 1
Private Const IDCLOSE As UInteger = 8
Private Const c As UInteger = MB_OK Or MB_ICONEXCLAMATION

Public Function messageThroughWindows() As String
Dim r As Integer = mb(0, "Click OK if you see this!", _
"Windows API call", c)
Dim s As String = "Windows API MessageBox returned " _
& CStr(r)& vbCrLf & "(IDOK = " & CStr(IDOK) _
& ", IDCLOSE = " & CStr(IDCLOSE) & ")"
Return s
End Function
End Class

(十三)——XML文档注释

安装了Visual Basic 2005 Express Edition Beta以后,我立刻被My的功能和新的项目属性窗口吸引了,泛型的智能感知也基本完善了。所以我后面将开始着重介绍这些最受人瞩目的功能。但是今天我还是按照计划,来介绍这个XML文档注释。

书写文档是程序员讨厌的任务之一,因为文档与代码的同步以前完全要靠手工进行。C#和Java引入的“注释文档”很大程度上简化了这个问题。它的基本原理就是将书写到代码中的特定注释提取出来,就可以生成代码的文档。现在,VB2005的XML文档注释给你的便利不仅仅如此,用了它你一定会爱上写文档。

Visual Basic 2005的文档注释是基于XML语法的,它可以描述代码中每一个类型和每一个成员的信息。在Visual Basic中只要输入'(连续输入3个单引号)就可以输入XML格式的文档注释。比如

'
' 说明部分
'

Public Class Form1

XML文档注释描述的是紧接注释部分的代码元素,一旦开始就不能断开,也不能掺杂其他代码元素,直到所有的XML标记被关闭。是VB推荐使用的标记,是该描述元素的摘要。除了,常用的标记有,表示说明;表示参数信息;表示异常,此外还有等等。不一定非要使用这些推荐的标记,但是这些标记使用时可以自动完成,而且能被IDE所利用。

XML文档注释写好以后,就可以用vbc.exe或者Visual Basic的IDE提取这些注释,生成所需的文档。你可以在项目属性对话框方便地找到生成文档的功能。下面就是生成文档的一部分,描述的是一个叫SubName的方法:



This is the summary of this member.




大写的M表示成员,而后面则是该成员的名称。有了这个XML文档,加上特定XSLT,就能生成一篇漂亮的报告。VB的编译器可以自动探测还没有编写文档的成员,还能检验特定成员的文档编写是否全面无误。

除了生成文档之外,书写XML文档注释最大的好处就是可以获得智能感知的支持。在VB5/6等早期版本,可以通过成员属性对话框为成员编写帮助,现在你只要写注释就行了。比如上面那个例子,当文档注释书写完毕后,再使用SubName的时候就会自动显示提示。

XML格式的文档提取出出来后将放到与程序集相同的文件夹中,这样其他项目甚至其他语言使用这些程序集时,也可以获得智能感知的提示。

XML文档注释的第二个好处是用来生成精美的报文。从“工具”菜单中选择“生成代码注释文档”就可以了,这样生成的报文能够快速察看所写代码的所有成员及说明。

XML文档注释是许多VB程序员盼望的功能,有许多第三方插件实现此功能,不过当然是官方的好用啦。

(十四)——My命名空间之My.Application

Visual Basic不同于Visual C#、Visual C++之处在于它更偏重于快速开发,更针对非专业开发人员和编程新手。Visual Basic 2005这次提供的“My”是一个极为出色的设计,可以帮助开发人员快速利用.NET Framework中的各种功能进行开发。说到My到底是什么,其实它就是一个工程相关的命名空间,其中的内容是由IDE帮助你组织的。

在My出现以前,.NET Framework已经具有强大而丰富的类库,学习这些类库算不上是一件轻松的事。许多VB或VC的开发者第一次接触到.NET开发时,总是习惯于自己实现或通过调用Windows API实现某些.NET早已准备好的功能。其原因就是.NET类库太庞大太分散了,许多常用的功能与那些不太常用的功能混在一起。比如,获得从当日零点开始的毫秒数的方法(经常被用来做随机数的种子)竟然与设置环境变量功能同在Environment类中,而不是“看上去像是”的System.Timers、TimeSpan或DateTime等命名空间或类型中。许多开发者对类库不熟悉,于是就一遍又一遍地重复开发.NET Framework的功能。Visual Basic Team为了解决这个问题,设计了My命名空间,它将.NET Framework中最常用的功能挑出来,然后按照最容易理解的逻辑结构存放在一起。当你深入My命名空间,你会发现那些功能就在你凭直觉就能想象到的路径中。

My命名空间在当前版本中主要包含My.Application、My.Computer、My.Resources、My.User、My.Forms和My.Webservices等六个主要部分。你可以输入My关键字找到他们,也可以导入My命名空间,其语法是:

Imports 项目名称.My

在My命名空间中的所有类或对象中,My.Application是与当前运行的应用程序有关的对象,本次首先来介绍My.Application。My.Application提供的功能非常丰富,比如当前应用程序的主线程、主窗口、版本或公司版权等信息、文化和语言设置、路径及命令行、事件日志甚至Splash Screen的信息。下面的表格列出了My.Application的全部功能。

My.Application 成员 描述
ApplicationContext 应用程序的上下文,包括主线程和主窗体的信息
AssemblyInfo 程序集信息,包括版本、版权、标题、产品名称和可执行名称等
ChangeCurrentCulture 改变应用程序当前文化设置,如货币和时间的格式
ChangeCurrentUICulture 改变应用程序当前的用户界面文化设置,如显示语言和用词
CommandLineArgs 一个只读集合,返回当前应用程序的命令行参数。这些参数已经分隔开,无须像原来那样手工分隔Command函数的值了。
CurrentCulture 返回当前的文化设置
CurrentDirectory 返回应用程序使用的当前目录
CurrentUICulture 返回当前的用户界面文化设置
Deployment 返回按照ClickOnce方法部署的应用程序的Deployment对象
DoEvents 执行储存在Windows消息队列中的所有Windows消息
Exit 退出应用程序
GetEnvironmentVariable 通过环境变量的名字获取环境变量的值
IsNetworkDeployed 返回一个值,指示当前应用程序是否采用了网络部署方式
Log 一个记录应用程序事件日志和异常的日志工具
MainForm 当前应用程序的主窗体
OpenForms 当前应用程序中所有已经打开窗体的集合,与VB6的Forms集合功能相同
Run 启动Visual Basic的启动/关闭应用程序模式
SplashScreen 返回当前应用程序作为Splash Screen的窗口

可以注意到,My.Application中的某些功能和Application对象是一样的,但是My.Application不仅仅能用于Windows Form的应用程序,许多功能在控制台应用程序照样能够使用。下面举几个简单的例子来使用My.Application:

1、显示一个简单的关于窗口。

With My.Application.AssemblyInfo
Dim msg As New System.Text.StringBuilder
msg.AppendLine("Protuct Name: " & .ProductName)
msg.AppendLine("Company Name: " & .CompanyName)
msg.AppendLine("Version: " & .Version.ToString)
msg.AppendLine("Description: " & .Description)

MsgBox(msg.ToString, MsgBoxStyle.Information, "About " & .Title)
End With

2、将当前打开的所有窗口的标题都改为环境变量%TITLE%的值

For Each f As Form In My.Application.OpenForms
f.Text = My.Application.GetEnvironmentVariable("TITLE")
DoEvents() '也可以写成My.Application.DoEvents()
Next

3、检查如果从网络上部署,修改当前用户界面文化设置为英语-美国

If My.Application.IsNetworkDeployed Then
My.Application.ChangeCurrentUICulture("en-US")
End If

还有很多很多不同的用法,大家可以亲自试试。有了My.Application,设置和获取应用程序信息变得非常容易和有趣。这才是使用Visual Basic真正的感觉。

(按:这项比较重要。)

作者:装配脑袋
出处:http://blog.joycode.com/ninputer/archive/2004/07/29/28805.aspx

(十五)——My命名空间之My.Computer

My.Computer可能是My命名空间中最有趣的部分了,这一部分封装了大量访问系统和硬件信息的功能,操作起来比直接使用.NET Framework或Windows API都方便得多。My.Computer中有很多对象,下面我们分别来介绍。

My.Computer.Audio

Audio对象提供了播放音频的功能,它既可以从wav等文件播放,也可以从音频数据流来播放,就是说你可以用它轻松播放储存在资源文件中或者数据库中的音频。播放时还可以指定后台播放或等待结束等多种设置。结合My.Resources来使用,更显得方便无穷。这是一个简单的播放wav文件的例子:

My.Computer.Audio.Play("c:\ding.wav", AudioPlayMode.BackgroundLoop)


My.Computer.Clipboard

Clipboard对象提供了以强类型方式读写剪贴板的功能,比Windows.Forms里面的剪贴板更加好用。使用Clipboard对象可以直接从剪贴板读写音频、图像、文本甚至我的电脑中的文件拖放信息。此外,由VB6升级的项目现在将直接使用My.Computer.Clipboard对象升级以前的Clipboard对象,这将解决VB.NET不能升级原先剪贴板功能的缺陷。下面的例子将文本框内的内容复制到剪贴板:

My.Computer.Clipboard.SetText(TextBox1.Text)

My.Computer.Clock

Clock对象是一个获取时间的工具,它可以直接获取当地时间、中时区的时间和从当时子时开始的毫秒计数。

My.Computer.FileSystem

这是微软Visual Basic Team在My.Computer中倾注最多精力的对象,使用它可以充分改善文件操作的复杂程度。FileSystem对象提供了易于理解的操作方式。FileSystem对象中复制文件的方法不但只需要指定目标路径,还可以帮助你建立目标目录中不存在的级别。它还特别提供了CopyDirectory的功能,可以复制整个目录!这正是目前.NET Framework缺乏的功能。同时FileSystem还能提供搜索上级目、子目录或根目录的功能,非常体贴。下面例子展示了如何在动画演示下将文件放入回收站。

My.Computer.FileSystem.DeleteFile("c:\mybigfile.big", True, True)

FileSystem对象还提供了只用一行代码就可以读取文本文件内容,或者将所需内容写入文本文件的功能,现在你不需要再用迷惑人的StreamReader、StreamWriter来读写文件了,还不用担心资源释放的问题。如下面的例子:

s = My.Computer.FileSystem.ReadAllText("c:\a.txt")

除了可以通过My访问以外,通过System.IO.FileSystem类也可以完成FileSystem对象的大多数功能,这种方式似乎更适合于使用C#或C++的开发者。

My.Computer.Info

看名字就知道了,这个对象的属性都是系统信息。如果你想获得本机物理内存或虚拟内存的总数,剩余量、操作系统名称、当前用户名、本机安装的文化设置等等,都可以轻松使用Info对象,它让你对应用程序所在的系统了如指掌。

My.Computer.Keyboard和My.Computer.Mouse

通过这两个对象,你可以快速获得用户键盘的信息,如大写锁定、数字键盘锁定等是否打开,以及鼠标有几个按键,是否配备滚轮等。如果你希望你的应用程序能够做到最体贴用户,那这些信息是少不了了。下面例子演示获取用户的鼠标左右键功能是否交换(这样你就可以知道用户是不是左撇子,从而提供更体贴的界面,多爽)

Dim f As Boolean = My.Computer.Mouse.ButtonsSwapped

My.Computer.Name

不用多说,这就是本机操作系统安装时输入的名称

My.Computer.Network

这个Network对象充分简化了最常用的网络任务,只需要一行代码,就可以Ping一个地址,或者检测网络是否接通。还能用一行代码下载或上传文件。比如这个例子就完成了一个下载文件的任务:

If My.Computer.Network.IsAvailable Then
My.Computer.Network.DownloadFile("http://abc.com/x.zip", _
"C:\download")
End If

My.Computer.Port

提供了用一行代码打开本机串口的功能,还能立刻绑定一个事件监视串口的变化。现在串口编程出奇的简单,再也不需要MSComm控件了。

My.Computer.Printers

这个Printers对象能够遍历本机所安装的所有打印机,还能找出默认的打印机。通过向默认打印机画图一样的操作,就能开始打印了。这样的操作会让你想起VB6时代便利而简洁的打印操作。下面的例子将在默认打印机上打印一个椭圆。从VB6升级项目时,原来的Printer对象将自动升级为My.Computer.Printers中的相关操作,升级的用户可以更加放心了。

My.Computer.Printers.DefaultPrinter.DrawEllipse( _
New RectangleF(2, 2, 50, 150), 1)
My.Computer.Printers.DefaultPrinter.Print()

My.Computer.Registry

这个注册表对象可比Microsoft.Win32空间中的那个版本简单多了,他提供强类型的路径支持,还能非常方便地读写注册表。下面的例子是一段内置的代码片断,演示了如何判断某一键值是否存在。

Dim exists As Boolean = True

Dim path As String = "Software\Microsoft\TestApp\1.0"

If My.Computer.Registry.CurrentUser.OpenSubKey(path) Is Nothing Then
exists = False
End If

My.Computer.Screen

Screen对象可以获取屏幕的可视范围,像素的位数等。比VB6的Screen对象更强的是,它现在支持两个显示器。

现在我们已经了解了My.Computer中的所有对象,这些对象将大部分任务简化成一行代码,对你的日常编程是不是非常有帮助呢?

(十六)——My命名空间之My.Resources和My.User

从原理上来说,My.Resources与前面介绍的My.Computer或My.Application是完全不同的,他带来的是另一种方便。My.Resource不是一个类库,而是My命名空间中唯一一个子命名空间。他的功能是什么呢?我们先回忆一下在.NET Framework1.1时代使用资源的情形。首先我们得通过工具,将图片、文本或声音等资源添加到资源列表中,编译成资源文件,再嵌入到我们的程序集中。当我们要使用资源的时候,必须通过System.Resources.ResourceManager从程序集中提取资源,然后自行判断资源的类型,做适当的转化并使用。比如从Form1的资源中取出ID为Greeting的字符串,需要写这么多代码:

Dim manager As New System.Resources.ResourceManage( _
"MyApp.Form1", Me.GetType().Assembly)
Dim s As String = manager.GetString("Greeting")

而且那个编辑resx的界面不太直观,只添加字符串资源就不太方便,要想添加图片、音乐等文件到资源文件并在程序中取用就更麻烦了。到现在,许多Visual Basic .NET程序员还总是询问将图片音乐嵌入到EXE文件中去的方法。.NET Framework 2.0为解决这个问题引入了一个新特性——强类型资源。首先ResourceManager增加了一个GetStream()方法,方便获取图片、声音类的资源,其次Resgen工具可以根据资源的内容生成一个包装类,通过它可以直接强类型地读些程序集内的资源。而Visual Basic 2005将强类型资源与VB的IDE的特性结合在一起,就成了方便无比的My.Resources。

在实际使用My.Resources之前,我们先看看Visual Basic 2005新的资源编辑器,它现在已经继承到了项目属性中。打开项目属性并切换到Resources选项卡,我们可以看到如下的资源编辑器。



从顶端的下拉列表框中,可以选择资源的类型——声音、图像、文本、文件、图标或其他自定义内容。选择相应的类别,下面的编辑器就会发生变化,以适应当前类型的资源。每种类别都可以添加任意数量的资源。比如我们添加一个名称为Greeting的字符串,并让它的值等于"Hello",然后切换到代码窗口,输入My.Resource.看到什么了?Greeting弹出来了。现在我们使用这个资源之需要一行代码了。对比刚才所述的旧方法,你感到方便之处了吗?

Dim s As String = My.Resources.Greeting

如果我们添加了图片资源,那么也可以直接使用My.Resources来访问,而且就是BitMap类型,你想怎样用它就可以怎样用它,比如:

BackgroundImage = My.Resources.MyPic

一行代码就可以轻轻松松将窗口背景设置为资源中的图片。如果储存了声音资源,那么结合My.Computer.Audio的功能,播放资源中的声音也变得如此简单:

My.Computer.Audio.PlaySound(My.Resources.MySound)

想象一下以前要多少代码才能完成这样一个任务!有了My.Resource,资源的使用变得非常简单,你一定会改变对使用资源的看法,而爱上在自己的程序中使用资源的。

My.User是My命名空间中最小的成员,但是别看他小,功能对于.NET新手来说却不简单。如果你初次接触.NET开发,要获取当前登陆用户的用户名和用户组怎么办呢?谁会想到它其实是和Thread.CurrentPrincipal属性有关呢?My.User简单地将用户名和角色信息提供给你,要想获得当前登录的用户名,只需要输入My.User.Identity就行了。

(十七)——My命名空间之My.Forms和My.WebServices

如果说My.Application、My.Computer和My.User是VB2005提供的汇集常用功能的类库,My.Resources是一个对项目资源的强类型封装,那么My.Forms和My.WebServices就是一个窗体和Web服务使用模式的绝佳范例。从VB6升级至Visual Basic .NET的程序员往往对VB.NET新的窗体编程模式不适应。因为VB.NET的窗体是类,必须要创建实例才能使用,而VB6的窗体则既是类又是对象,无须创建实例就能使用。在VB.NET中,往往要用这样的语法来使用窗体:

Dim frmForm2 As New Form1()
frmForm2.Show

然而用这样的语法显示窗体,各个窗体之间的通信或数据传递就十分困难。比如新生成的frmForm2要想访问另一个窗体Form1就难以做到,因为frmForm2无法获得Form1实例的引用。许多初学者在使用窗体的时候弄不清类、实力和引用之间的关系因此常常遇到苦难。即使熟悉了这些概念,有时仍不能用正确的方法解决窗体互相访问的问题。许多解决方案,如通过构造函数传递数据,通过全局变量或者静态变量,甚至在模块中生成项目中所有窗体的实例等等都不是十分理想,他们会增加窗体之间的耦合性,或者浪费内存。为了彻底解决窗体创建和互相访问的问题,Visual Basic 2005引入了My.Forms。My.Forms虽然在My命名空间中,但是使用它不需要输入My.Forms。假设你有两个窗体——Form1和Form2,Form1是启动窗体,现在你要用代码显示Form2,新的语法是这样的:

Form2.Show

这种用法和VB6几乎一样。Form2是窗体的类,怎么可以直接使用了呢?因为My.Forms为项目中每一个窗体维护了一个默认实例,其实现方法很像Singleton模式——每个窗体都有一个默认实例,而且有一个全局访问点,就通过窗体的类名即可访问到。这种方式彻底解决了窗体互相访问的问题,因为每个窗体都可以随时访问到任何其它的窗体的默认实例。比如要在Form2中修改Form1中一个TextBox的文字,只需要这样:

Form1.textBox1.Text = "Hello"

不在需要任何传递参数的构造函数或者静态/全局变量。一个项目中多数窗体都是只需要一个实例的,所以这种模式适合任何项目使用。无论是新手或老手,我都建议尽情使用My.Forms的功能,他是解决窗体互访的最佳模式,同时不会浪费内存,因为它只有在第一次访问所需窗体的时候才建立它。

My.WebServices的原理与My.Forms如出一辙,因为原来WebService的代理类都需要手工创建对象才能使用。而WebService对于项目全局应该有一个一致的访问点,所以VB2005将代替你创建代理类的实例,并维护于My.WebServices中,你可以随时访问他。比如你的项目添加了一个Web引用到Service1服务,它提供了一个方法叫Method1,以前的Visual Basic你必须写成:

Dim myService1 As New Service1()
Dim myResult As Integer = myService1.Method1()

而现在,无论在何地,你都可以直接写:

myResult = My.WebServices.Service1.Method1()

而无须手工创建代理类的对象了。

到今天为止,我已经介绍了My命名空间中的六个主要的功能区域,还剩下一个My.Settings,由于它在目前的BETA版本中还有缺陷,所以我将在以后找机会介绍它。下一次,我将介绍如何在C#和其他语言中使用My命名空间,以及如何通过编程扩展My命名空间的功能。

(十八)——My命名空间之高级用法
我已经将My命名空间中所有默认的对象都介绍完了,相信大家已经开始体验到My的方便之处。但是对于一些高级用户来说,这些功能还显得有所欠缺。老手们有时也编写了类似My功能的类或函数,要是能把他们放到My命名空间中多好。My就像一个可随时访问的工具箱,除了里面已经有的工具以外,当然允许我们将自己的东西放进去。下面我们就来看看怎么扩展My命名空间。

添加自定义的类或模块

如果我们想要放进My中的函数都是静态的,那么直接把类或模块放入My命名空间是个方便的方法,做法非常简单,只要将类或模块定义在My命名空间中即可

Namespace My
Public Module Tools
Public Sub DoSomething()
'Some code here
End Sub
End Module
End Namespace

现在就可以直接用My.Tools来访问自定义的模块,很方便。

添加自定义类的实例

如果我们观察Visual Basic所提供的My命名空间成员,将发现他们都不是类本身,而是对象。因为所有的方法都做成静态毕竟不是最佳方法,我们有时候需要将一些自定义类的实例放到My命名空间中,这样不用的时候就比较节省内存,而且易于控制资源释放的问题。首先我们要定义自定义类,可以放在任何地方,而不必放到My命名空间中,这样就可以避免类名直接显示在My关键字后。然后,在My命名空间下,定义一个带有HideModuleNameAttribute的模块,名称可以随便起;最后在模块中设定访问自定义类实例的属性。假设我们的自定义类叫ToolsProxy,而自定义属性叫Tools,那么可以这样写:

'Namespace My

_
Friend Module MyCustomModule
Private syncRoot As New Object

Private _tools As ToolsProxy

''
'' Contains my custom tools.
''

Public ReadOnly Property Tools() As ToolsProxy
Get
'Double check lock to ensure thread safty.
If _tools Is Nothing Then
SyncLock syncRoot
If _tools Is Nothing Then
_tools = New ToolsProxy
End If
End SyncLock
End If

Return _tools
End Get
End Property
End Module

有了HideModuleName这个Attribute,它本身就不会出现在My关键字后面,而它的属性则会显示。完成以后,你会发现My.Tools这次与Visual Basic内置在My中的对象没有任何区别了。此方法为扩展My最佳的方法,推荐使用。

扩展My.Application或My.Computer

有时候,我们不仅要将自定义的类添加到My中,还希望更进一步直接补充My.Application或My.Computer本身的功能。而这完全可以做到。Visual Basic提供了Partial关键字,他可以扩写当前项目中的类或结构,无论原先定义的时候是否加上了Partial关键字。于是我们就可以利用这一特征扩写定义看不见的My.Application或My.Computer。My.Application对应的是MyApplication类,而My.Computer对应的是MyComputer类。比如我们要给My.Computer增加一个新的功能——CdRomDriver属性,用以控制光驱的弹出、缩进与自动运行,就可以直接在My命名空间下扩写MyComputer类,如下:

'Namespace My

Partial Class MyComputer
Public ReadOnly Property CdRomDriver() As CdRomDriver
Get
'Codes here
End Get
End Property
End Class

这样就成功地给My.Computer添加了新的属性,你会发现My.Computer.CdRomDriver和其他My.Computer有完全一样的行为。同样,My.Application也可以通过Partial并添加自定义属性的方式进行扩展。

现在我们可以看出,My命名空间是完全开放和可编程的,我们可以自由发挥想象力,创造完全属于自己的My命名空间。

也许说到这里,C#的程序员已经很眼馋了,能不能在C#中使用My命名空间呢?答案是能,又不能。C#的设计者没有提供通过My来访问对象的方法,那么直接使用My是不可能了。但是Visual Basic的设计者已经考虑到了这些问题,将My命名空间中大多数功能开放了出来,让C#和其他语言的程序员可以用到他们。

在C#中使用My.Application

要使用My.Application,必须要继承System.Windows.Forms.WindowsFormsApplicationBase(该类位于Microsoft.VisualBasic.dll中)。继承之后我们要做几件事情,首先书写构造函数初始化MyApplication类,在Visual Basic中这些设置均为IDE生成,而C#则需要手工书写。然后重写OnCreateMainForm方法,这个方法设定了当前应用程序的主窗体。接下来书写My类,让他提供对MyApplication类的全局访问点。最后改写Main,在其中执行My.Application.Run()代替原来的Application.Run(),具体做法如下。

namespace WindowsApplication2.Properties
{
public class MyApplication : WindowsFormsApplicationBase
{
public MyApplication() : base(AuthenticationMode.Windows)
{
IsSingleInstance = false;
EnableVisualStyles = true;
ShutdownStyle = ShutdownMode.AfterMainFormCloses;
}


protected override void OnCreateMainForm()
{
//Sepecify the main form of your project
MainForm = new WindowsApplication2.Form1();
}

}

public static class My
{
private static MyApplication _Application;
private static object syncRoot = new object();

public static MyApplication Application
{
get {
if (_Application == null)
{
lock (syncRoot)
{
if (_Application == null)
{
_Application = new MyApplication();
}
} // end lock
} // end if
return _Application;
} // end get
}

}
}

修改Main函数:

[STAThread]
static void Main()
{
// Application.EnableVisualStyles();
// Application.EnableRTLMirroring();
// Application.Run(new Form1());
Properties.My.Application.Run();
}

这样我们就充分获得了My.Application的对象模型。但是有些动态生成的内容,如SplashScreen对象等,除非自己书写,否则无法使用。通过这种方法使用My.Application可以获得许多额外的好处,比如获得My.Application的事件支持,他们是跟应用程序运行状态密切相关的事件,非常有用。但是由于缺乏自动代码的支持,不能保证My.Application事件的全部行为都和Visual Basic一样。

在C#中使用My.Computer

要使用My.Computer我们无需像Application那样费劲了。Visual Basic的设计者将大部分供能放到了System的自命名空间中,可以很自然地使用他们:

My中的名称 System中的名称
My.Computer.FileSystem System.IO.FileSystem
My.Application.AssemblyInfo System.Reflection.AssemblyInfo*
My.Computer.KeyBoard System.Windows.Forms.KeyBoard
My.Computer.Mouse Sysetm.Widnows.Forms.Mouse

注:System.Reflecton.AssemblyInfo只是My.Applicaton.AssemblyInfo的类型。

当然这不如Visual Basic中那样丰富,但是一般应用有FileSystem就足够了,其他功能用.NET Framework原本的方法也很简单。要注意,尽管这些类分散在System的各个字命名空间中,但是全部都位于Microsoft.VisualBasic.dll中,需要引用它才能使用。

在C#中使用My.Resources和My.Settings

一个好消息是,C#也内置了这两样功能,只要把My换成Properties就可以使用了,和Visual Basic一样方便。但是My.User、My.Forms和My.WebServices是真的没有了,要实现他们的功能,需要完全手工编码。

(十九)——用属性窗口编辑Attribute

(二十)——泛型 part.1

这个系列总算走到了Visual Basic最后一个大的语言改进——泛型。事实上,泛型是.NET Framework 2.0所支持的一项特别的功能,Visual Basic 2005只不过从语言层面上支持他,就像C#和C++/CLI一样。首先,我们从泛型本身介绍起。

需求

我们常常会有一种需求,就是我们编写的代码能够针对多种类型执行。比如排序,检索,集合的操作等等。这些操作的代码应该能够只编写一次,就能够广泛地用于所有类型。.NET Framework 1.1或更早版本提供的方案是,用继承树的根——Object,承载任何类型。这样针对Object的算法就等于兼容于所有的类型。但是以Object作为所有类型的承载容器有两个重要的问题:首先值类型在转换成Object时要进行“装箱”操作,该操作的速度非常缓慢,因此性能就成最大的问题。其次是对Object进行操作必须通过运行时类型转换才能进行,这样就不能在编译期间发现类型不兼容的操作。比如我们有一个自定义的结构——Customer,我们希望用ArrayList来保存一个Customer的列表:

Dim customerList As New ArrayList()
customerList.Add(New Customer("Harry Potter", 13, "Hogwarts School"))

'当我们要使用这条记录时
MsgBox(CType(customerList(0), Customer).Name)

初看起来这没有任何问题,但是如果我们添加的语句写成这样:

customerList.Add("Hello")

会怎么样呢?String根本不能转换成Customer类型,但是没有任何提示阻止你这样做。这个错误直到运行时才会体现出来。我们需要强类型的方法,编译时的类型检查和更高的性能,所以我们需要泛型。

感受泛型

现在我们就来看看针对上述需求的泛型解决方案。.NET Framework 2.0支持一种新的泛型列表——List(Of T)。这里我们引入了Of语句,他就是Visual Basic为实现泛型而增加的。其中T称为“类型参数”,它接受任意一个.NET类型作为元素的类型。于是我们可以将上述Customer的代码写成:

Dim customerList As New List(Of Customer)
customerList.Add(New Customer("Harry Potter", 13, "Hogwarts School"))

MsgBox(customerList(0).Name)

我们来看看这段代码中改变的地方。首先List(Of Customer)对T进行了指定,所以现在customerList对象就是一个只能装Customer类型元素的列表。这件事是在编译期间决定的,因此编译期始终知道customerList元素的类型,所以在取出其中对象时无需再进行任何类型装换。更重要的是,现在再向customerList中放入不是Customer类型的变量就会出现编译错误,而不是原来的运行时错误了。泛型能让编译期明确正在操作的类型,它就不会对值类型进行装箱操作,于是性能也大大提高了。使用泛型的另外一个好处是,Visual Basic的IDE能够为你提供智能感知,当你使用customerList.Add时,含有正确信息的提示出现了:


开始编写泛型的代码

泛型提供给你的不仅仅是使用.NET Framework中已经设计好的泛型类型,你完全可以自己书写泛型的代码。仍然是使用Of语句:

Class MyGeneric(Of T)

这时,Of语句的作用不再是为类型参数提供所需的类型,而是定义新的类型参数。现在MyGeneric就接受一个名为T的类型参数。在MyGeneric泛型类的内部T被看作一个类型。你应当将T想象成使用该泛型类时能在Of语句之后提供的任何类型。现在只要对T进行编码就行了,比如:

Class MyGeneric(Of T)
Private myVar As T

Public Sub SetVar(ByVal newValue As T)
myVar = newValue
End Sub

Public Function GetVar() As T
Return myVar
End Function
End Class

相当简单吧。当你要使用这个泛型类的时候,如同Framework提供的泛型类一样,要指定类型参数T的真实类型。如这一语句:

Dim obj As New MyGeneric(Of Integer)

这时obj的类型实际上就是一个将上述定义中所有T都换成Integer的类型。而

Dim obj As New MyGeneric(Of String)

这条语句中,obj则是一个T为String型的实例。你可以任意指定类型参数,以便创建出更多适于不同类型的对象,这就是泛型的精髓——“书写一次,使用于广泛的类型”。

泛型不仅仅能用于类型的定义,你还能够定义泛型的方法。其语法和泛型类型基本一样:

Public Function IIf(Of T)(Expression As Boolean, TruePart As T, _
FalsePart As T) As T

这就是一个泛型方法,其参数和返回类型都可以使用类型参数T所表示的类型。泛型方法可以像泛型类型那样,使用Of语句来确定类型参数T:

max = IIf(Of Integer)(a > b, a, b)

通过指定类型参数为Integer,我们的IIf(Of T)函数就成了专门针对Integer的IIf函数。其实,Visual Basic还支持类型参数的隐式指定,就是说当参数或返回值能够有足够的信息确定类型参数时,Of语句就不必写了。比如

Dim max, a, b As Integer
a = 100
b = 50

max = IIf(a > b, a, b)

着段代码中a和b的类型已经都确定为Integer因此IIf(Of T)函数不必通过明确制定类型参数也能知道此处的类型参数应该是Integer。这将大大简化代码的书写,又不失泛型带来的性能好处。

(二十一)——泛型 part.2

尽管在周末发帖子不符合sumtec的Blog守则,但我确实无从选择。每天6点多出家门8点到家10点睡觉的生活,让我找不出空余时间来写Blog。我只能尽量用周末的时间为我贫乏的Blog补充几篇帖子。

今天我继续讨论泛型。上次我们已经了解到.NET泛型在减少装箱和编译时类型检查方面的优点。今天我们继续深入泛型给.NET开发带来的变化。

泛型的类型系统

泛型类型(Generic Type)通过赋予类型参数,在使用时可以表现为多种构造类型(Constructed Type)。如TheClass(Of T)在使用时可以是TheClass(Of Int32),也可以是TheClass(Of String)他们都是不同的类型。这些类型之间没有任何继承关系,也没有互相包容的功能。TheClass(Of Object)并不是其他TheClass(Of T)构造类型的基类,也没有一种写法可以表示类型参数为任意类型的情况。这种类型衍生往往会连带泛型类内嵌类型一起衍生。比如在TheClass(Of T)中内嵌定义有枚举(或结构、委托、类等)TypeA,则TheClass(Of Int32).TypeA和TheClass(Of String).TypeA也非相同类型。在.Net Framwork的类库中,这种情形频繁出现,如List(Of T).Enumerator。因此,注意让你的内嵌类性与泛型的类型参数有关,否则就应该将内嵌类型定义到泛型类型的外部,以免发生类型衍生。一般不要在泛型类型中内嵌定义另一个泛型类型,如ClassA(Of T1).ClassB(Of T2),他在衍生类型的时候就更复杂了,因为两个类型参数都可以赋予不同的类型,会产生大量你也不知道有什么用的构造类型。

.NET Framework还增强了反射的功能以便在运行时研究泛型类型及其所有构造类型。每个构造类型都有确定的类型,可以通过obj.GetType()获取运行时类型的Type对象,还可以用GetType运算符获取泛型类型本身或其任何构造类型的Type对象。现在Type类有HasGenericParameters属性以指示此类型是否源自泛型类型,以及HasUnboundGenericParameters属性以指示此泛型类型的所有类型参数是否已经确定等。你还可以通过Type对象的相关方法检测泛型类型每个类型参数的详细状况。

约束

约束(Constraint)这一功能的本意是缩小类型参数所能取值的范围。比如你希望你的类ClassA(Of T)中类型参数T只能接受Exception或其子类等要求,可以通过约束这一功能达成。约束会带来一种“副作用”,事实上更多人把这个副作用当成约束的主要功能来用,我们下边详细叙述。约束的语法是:

Definition(Of TypeParam As Constraints)

比如我们要给ClassA定义类型参数T,并约束T只能为Exception或其子类的定义为:

Class ClassA(Of T As Exception)

如果我们要写ClassA(Of Integer)就会产生编译错误,因为T已经不能去Int32作类型参数了。T的取值范围已经缩小,除此之外约束还有什么功能呢。我们可以在ClassA中定义一个方法:

Public Sub ThrowIt(ByVal ex As T)
Throw ex
End Sub

我们写了Throw ex,为什么可以这样写?可以试试将类型定义中的“As Exception”去除,这里立即就会有编译错误。这是因为.NET默认情况下不允许对类型参数的对象施以任何操作,除了能对Object类型进行的操作以外。就是说默认情况下T类型的对象没有除Object类型所具有的方法/属性以外的任何成员,也不能使用任何运算符。这通常是一个很大的限制。但当你使用约束时,类型参数就会自动具有继承自约束类型的所有功能。比如继承自Exception就可以用于Throw语句,而约束了Exception的类型参数T的对象同样可以用于Throw语句。我们常常这样写:

Class ClassB(Of T As IDisposable)

这样T的取值范围就被限定在实现了IDisposable的所有类型中,而副作用是T型对象具有IDisposable接口的成员:Dispose()方法。比如书写这样的过程。

Public Sub DisposeIt(ByVal obj As T)
obj.Dispose()
End Sub

我要强调约束的主要功能是减少类型参数的取值范围,而不是给类型参数增加可操作的功能(那只是副作用)。一般不要为了这种目的给类型参数加约束。比如你想让操作对象有Dispose方法,不妨用这种写法代替约束:

Public Sub DisposeIt(ByVal obj As IDisposable)
obj.Dispose()
End Sub

只有当你的类型参数一定要有某种功能,否则整个泛型类都无法工作的时候,才使用约束。不然你会发现约束了很多类型后,你的泛型类型的用途大大减少了。

还有一种特殊的约束——构造器约束。他约束你的类型参数只能取那些有一个不带参数的公有构造器的类型。其副作用是,你可以在这个泛型类中创建类型参数的实例:

Public Class ClassB(Of T As New)
Public Sub MySub()
Dim o As T
o = New T()
End Sub
End Class

注意构造器的写法:用New关键字作约束类型。构造器约束看起来很有用。

如果你要约束两个以上的类型,你可以把要约束的类型放在花括号里{},就像数组一样。比如你的类型参数同时需要约束构造器和IDisposable,就这样写:

Public Class ClassC(Of T As {IDisposable, New})

你还可以用多约束做一些以前只用接口做不到的事情,比如这样:

Sub MyMethod(Of T As {IDisposable, ICloneable})(ByVal obj As T)

这个定义实现了让参数obj的类型只能为同时实现了两个接口的类型,不用泛型你以前可以做到吗?

(按:ninputer的工作总是简明平实,我很喜欢)



<< Home

This page is powered by Blogger. Isn't yours?