与Visual Studio类似,Visual Basic也将从版本12直接跳到14。虽然新版本中的许多特性对于C#来说也是首次引进,但仍然有大量的功能增强是特别针对VB的,旨在简化VB的使用。本文列举了一些最令我们感兴趣的特性。
新版本的一个特性是对null值的支持,该特性使用?.操作符。这一特性与C#相同,如果操作符左方的表达式返回值不为null,则继续计算右方表达式。在处理外部资源所返回的少量数据时,该特性尤其有用。举例来说:
If customer.PrimaryResidence IsNot Nothing AndAlso customer.PrimaryResidence.Garage IsNot Nothing AndAlso property.PrimaryResidence.Garage.Cars = 2 Then Print("Two Car Garage")
这段代码将被简化成以下语句:
If property.PrimaryResidence?.Garage?.Cars = 2 Then Print("Two Car Garage")
除此之外,还可以将该操作符与If操作符进行结合,实现为表达式提供某个默认值的功能:
Dim numberOfCarPorts = If(property.PrimaryResidence?.Garage?.Cars, 0)
C#与VB并不是唯一两种支持这种null处理方式的语言。在Apple的产品中得到广泛应用的Objective-C语言默认就支持该行为。尤其是它的方法调用也使用.操作符,其工作方式就类似于VB中的?.操作符。
在Objective-C的社区中,人们对该特性的评价褒贬不一。某些开发者非常喜爱这项功能,因为他们在进行方法调用时无需担心空引用异常的产生。而另一些开发者则对此感到痛恨,因为在问题发生时,他们不会看到空引用异常的产生,只会看到方法调用失败。如此一来他们就会感到困惑,为什么方法返回了null,而不是返回有效值或抛出异常。
在Visual Basic 12中,我们首次看到了CallerNameAttribute这一特性的引入。虽然这一特性解决了属性变更通知(property change notification)的问题,但它的通用性还不足以解决另外一部分问题,在这些问题中需要一个以字符串形式表达的唯一标识符,在这种情况下,需要使用到NameOf这个操作符。
以下这个示例是由来自Visual Basic团队的 Lucian Wischik所提供的 ,其中包括对参数进行验证的逻辑。
Function Lookup(name As String) As Customer If name is Nothing Then Throw New ArgumentNullException(NameOf(name))
这种方式能够避免在修改了参数名称的时候,忘记修改了所抛出异常的构造函数中所定义的字符串。由于NameOf操作符实际上创建了一个常量,因此你可以在任何需要使用硬编码字符串的时候使用该操作符。
自从十年以前.NET初次问世的时候,String.Format这个方法就要求开发者们对参数的数量进行计数。多年以来,由于计数错误所产生的bug可谓是不计其数。字符串插值这一技术最初是由Mono团队为C#语言所创建的,它彻底解决了计数这种糟糕的做法。
插值字符串是由$”开头的,而不是单单使用”。对于每个你需要插入值的位置,都要使用一对大括号进行转义,这一点与String.Format的做法是相同的。另一个与String.Format相同的地方在于可以在转义中加入格式化选项。在下面这个简单的示例中出现了两个变量,name和total,后者将被格式化为货币格式。
Dim message = $"Hello {name}, your amount due is {total:C2}"
该语法本身就使用了String.Format方法,因此使用者同样需要注意在适当的场合进行转义,考虑一下以下字符串:
Dim requestUrl = $"http://{server}/customer?name={customerName}"
这段代码会产生一个bug,开发者实际上需要的是以下代码:
Dim requestUrl = $"http://{server}/customer?name={UrlEncode(customerName)}"
乍一看插值字符串的语法,似乎无法处理从外部资源中获取字符串的场景,例如从本地化表或资源字典中获取字符串。不过,微软正在努力实现这一功能。Lucian Wischik写道:
不仅能够使用在不同的语言文化中,而且还能够从中抽取出原始的格式化字符串或者是参数(举例来说,如果你打算在SQL查询中使用该语法,或者会需要对参数进行转义,以避免产生字符串注入攻击)。但目前为止,我们还没有完全决定该语法的设计规格。
按照当前的 规格声明草稿 所说,插值字符串可以是一个常规的字符串,也可以由一个名为FormattedString的对象实现。当你试图将某个插值字符串赋值给一个实现了IFormattable接口的变量或是参数时,系统会自动创建一个FormattedString类型的实例。
该对象的IFormattable.ToString方法接受一个类型为IFormatProvider类型的参数,使用该参数能够重写格式化相关的行为。
string IFormattable.ToString(string ignored, IFormatProvider formatProvider) { return String.Format(formatProvider, format, args); }
在上面一段代码中,format与args两个参数分别代表了待插值的原始字符串,以及它所对应的值。
在VB中新加入的一个特性是多行字符串。实现它不需要任何特殊的语法,只需要在希望分行的地方省略引号即可。根据源代码文件所使用的换行符的不同,该换行符会自动在vbCrlf、vbCr及vbLf等符号间进行选择。对于Visual Studio的用户来说,基本上都会选择vbCrlf。
在目前,某些开发者会选择在XML文本中使用CData段落来模仿这一特性,这种方式虽然能够实现所需要的效果,但显得有些冗长与笨拙。
自动属性现在可以标记为只读了。可以在声明时为该属性赋值,也可在构造函数中进行赋值。
该语法的使用方式应该不会出乎你的意料:
Public ReadOnly FirstName As String = "Anonymous" Public ReadOnly LastName As String Public Sub New (firstName As String, lastName As String) Me.FirstName = firstName Me.LastName = lastName End Sub
在使用这一特性时,应当考虑到某些特殊情况。要理解这些情况,你首先必须理解参数传递的copy-in和copy-out概念。CLR只允许你为变量及字段进行引用传递(即C#中的ref或out操作符)。但在VB中,你也能够为属性进行引用传递。
为了缓解这两者之间的分歧,VB会在准备进行函数调用时创建一个本地变量,该属性的值会被拷贝到这个本地变量中。该本地变量随后被传递至函数中,函数体能够修改该本地变量的值。当该函数返回时,本地变量的值会拷贝回属性中。
在使用只读的自动属性时,将会应用以下规则:
这些规则都是基于只读字段的工作原理所产生的。
现在,在一个多行语句的每一行末尾都可以加入注释了。在之前的版本中,只能在多行语句的最后一行末尾加入注释。请看以下示例:
Dim emailList = From c in Customers Where c.IsActive 'ignore inactive customers And Not c.DoNotEmail 'we don’t need another spam violation Select c.FullName, c.EmailAddress
结构体现在能够支持无参构造函数了。虽然CLR本身就支持这一特性,但还没有主流的编程语言实现了这一特性,其原因是构造函数的运行时机并不明确。举例来说,在创建某个结构体的数组时,该结构体的构造函数并不会运行。
如果你的代码是myStruct = new MyStructure(),那很显然该构造函数会立即执行。而如果你的代码是myStruct = Nothing,则显然不会执行构造函数。但在某个本地变量或成员变量自动初始化时又是否会执行构造函数呢?无论你选择哪一种答案,总会让一部分人感觉不爽。
从今年开始,数据文本(对于JSON格式来说非常重要的一个特性)终于改为使用符合ISO标准的格式了。在过去,数据文本一直使用基于美国的格式化形式,对于居住在欧洲的人来说就会产生一些迷惑。
Overrides修饰符将会隐含使用Overloads修饰符。在过去,VB的开发者必须同时使用这两种修饰符,才能保证C#的使用者在使用由VB所创建的类库时能够调用正确的重载方法。
在C#中使用接口继承这一特性时,会造成不易判断到底是哪个接口方法被调用的问题。在VB中不允许出现这种场景,但由于C#允许这一特性,会造成出现某些VB无法实现的接口的情况。(在Microsoft Dynamics的某个产品中就数次出现这种情况。)
相对于C#中所使用的“通过名称隐藏”(hide-by-name)的重载规则,VB 14中将对这一限制进行放宽,转而使用一种(对VB来说)更传统的方式,即“通过签名隐藏”的规则。
VB也曾在命名空间解析这一问题上栽过跟斗,考虑一下以下代码:
Threading.Thread.Sleep(1000)
按Lucian Wischik所说:
之前,VB会尝试查找“Threading”这一命名空间,由于它无法分辨System.Threading和System.Windows.Threading的区别,因此直接报错。现在,VB14会同时支持这两种可能匹配的命名空间。如果你在代码编辑器输入Threading.,那么在输入.号之后,你会在智能提示中看到对这两个命名空间的支持。
类似的情况还有许多,举例来说:在编写Winforms应用时,ComponentModel.INotifyPropertyChanged事件就会无法分辨System.ComponentModel及System.Windows.Forms.ComponentModel,这一问题如今将不复存在。
微软在十年前就 创建了IsNot操作符 ,自那以来,就不断有VB的开发者要求微软允许在TypeOf表达式中使用IsNot操作符,举例如下:
If TypeOf sender IsNot Button Then
VB 14为预处理指令提供了两点改进之处。
Region将能够在函数体中进行使用,甚至是跨两个函数体进行使用。
与C#相同,Visual Basic现在也能够关闭对某一个代码块的编译警告了。在规格说明中提供了一个示例:
#Disable Warning BC42356 'suppress warning about no awaits in this method
通常来说,开发者会通过某个指令在该代码文件的其它地方重新打开这一警告
#Enable Warning BC42356
如果该警告的ID中包含了空格或标点符号,则必须使用引号。微软的工具不会自动为你完成这一点,不过由Roslyn所编写的第三方分析器规则或许能实现这一点。
VB的快速修复(Quick Fix)特性能够通过自动添加这些指令实现绕过某些警告的目的。这一点对于之前提到的第三方分析器规则来说尤其有用,因为你不一定能够很快地找到对应的ID。
目前来说,VB编译器会忽略XML文档的内容。而在VB 14中,编译器就会试图在文档中查找错误,例如不正确的参数引用名称。它还能够“正确地处理crefs标签中的泛型与操作符”。
与类和结构体类型,你现在能够将模块与接口声明为部分(partial)了。通常来说,这一特性是为代码生成器所准备的,但也能够在跨多个平台分享代码时发挥作用。
Jonathan Allen 的第一份工作是在二十世纪90年代后期时,参与某个医疗诊所的管理信息系统项目的开发,将该项目由Access及Excel逐渐转化为企业级解决方案。随后,当他为某个商业部门的自动交易系统工作了五年之后,他决定转为进行高端用户界面的开发。在空余时间,他喜欢阅读及撰写一些关于西方武术在15世纪至17世纪之间发展的文章。
查看英文原文: Article: Visual Basic 14 Language Features