对于 C#的两个重要特性元组和 Ref 返回值,Visual Basic 15 提供了对等的实现。这两个特性都是“不完全的”,但已经可以提供足够的变通方案,让 VB 应用程序可以消费使用了这些特性的 C#库。
元组
在 VB 中,从单个函数调用直接返回多个值是一个人们期待已久的特性。虽然开发人员可以使用 ByRef 参数获得同样的结果,但与函数式编程语言相比,其语法相当笨拙。
你可能不熟悉这个术语,“元组”就是一组相关的值。从.NET 4.0 开始,Visual Basic 就提供了一个标准的 Tuple 类,但用户体验不那么令人满意。开发人员必须手动解包来获得 Tuple 的值,而且要使用 Item1、Item2 等不能提供什么帮助的属性名。
七年之后,VB 15 终于为元组提供了语法支持。更确切地说,与堆分配 Tuple 对象相比,ValueTuple 结构提供了更好的性能。下面是一个例子,TryParse 方法是使用新的风格编写的:
Public Function TryParse(s As String) As (Boolean, Integer) Try Dim numericValue = Integer.Parse(s) Return (True, numericValue) Catch Return (False, Nothing) End Try End Function Dim result = TryParse(s) If result.Item1 Then WriteLine(result.Item2) End If
在这个例子中,TryParse 的返回值是 ValueTuple。这使得函数更简洁了一些,因为你不必再显式说明 ValueTuple 类型。但是,如你所见,调用它的代码没有任何不同。因此,让我们稍微改进一下,但保持返回类型的字段不变:
Public Function TryParse(s As String) As (IsInteger As Boolean, Value As Integer) … End Function Dim result = TryParse(s) If result.IsInteger Then WriteLine(result.Value) End If
你可以使用下面的语法创建带有命名字段的元组:
Dim kvPair = (Key := 5, Value := "Five")
遗憾的是,VB 没有提供一种可以将元组“解包”到多个变量的方法。因此,将下面这行 C#代码翻译成 VB 时,每个变量需要一行:
var (key, value) = kvPair;
(By)Ref 返回值
Ref 返回值,也就是 VB 中的 ByRef 返回值,有严格的限制。你可以消费将一个引用(即托管指针)返回给一个字段或数组索引的 C#函数,但你无法自己创建,你也无法创建局部 ByRef 变量。
你所能做的是采用一个相当复杂的变通方法。虽然你无法创建局部 ByRef 变量,但你可以创建 ByRef 参数。考虑下下面的 C#函数和它在 VB 中的等价实现:
public ref string FindNext(string startWithString, ref bool found) ByRef Function FindNext(startWithString as string, ByRef found as Boolean) As String
要使用这个函数, Klaus Löffelmann 告诉我们,我们需要一个辅助函数:
Private Function VbByRefHelper(Of t)(ByRef byRefValue As t,byRefSetter As Func(Of t, t)) As t Dim orgValue = byRefValue byRefValue = byRefSetter(byRefValue) Return orgValue End Function
接下来,你可以将返回 ref 的函数的结果作为参数传递给辅助函数,同时,你希望将其作为一个单独的匿名函数。
Dim didFind As Boolean '版本#2: 借助一个简单的泛型辅助类: aSentence = New NewInCS2017.Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.") Do VbByRefHelper(aSentence.FindNext("Adr", didFind), Function(stringFound) As String If stringFound = "Adrian" Then stringFound = "Klaus" Return stringFound End If Return stringFound End Function) Loop While didfind
以下是 VB 完全实现 Ref 返回值后的样子,仅供参考:
'THIS DOES NOT WORK IN VB!!! Dim aSentence = New Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.") Dim found = False Do ' !!In C# we can declare a local variable as ref - in VB we cannot.!! ' This variable could take the result... Dim ByRef foundString = aSentence.FindNext("Adr", found) If foundString = "Adrian" Then ' but via the reference, so writing would be possible as well, ' and we had a neat find and replace! foundString = "Klaus" End If Loop While found
那么,VB 为什么没有实现 ref 局部变量呢?
从根本上说是因为还没有东西使用它们。在.NET API 中,目前没有任何东西需要使用它们,这个特性最终可能还是会走进死胡同。这以前发生过;几乎没有人使用不安全块,即使在使用原生 C 程序库时。栈分配数组的情况也一样。
Anthony Green写道:
坦白说,VB2017 提供消费 ref-returning 函数的功能是为了对冲未来的不确定性,而不是为了让那个特性成为主流(即使在 C#中也是如此)。
[…]
如果你看一下 C#的这个版本,该特性带来了另一个特性,即 ref 局部变量。关于 ref 局部变量,也有一些考虑,ref 再赋值(将一个局部变量“指向”另一个位置)以及 ref 局部变量默认只读,如何在所有可以引入变量的地方指定 ref,区分普通赋值和 ref 赋值,类似这样的决策都需要在 VB 中解决,以便获得同样的生产能力。有大量的工作要做。
在 VB 中,工作量会更大,因为 VB 有额外的功能使用了 ByRef,比如传递 ByRef 属性。VB 编译器开发负责人 Jared Parsons 有一篇不错的博文,列举了许多在 VB 中使用 ByRef 的案例,可以说明这一点。返回一个 ByRef 属性,并在一个后期绑定的上下文中处理 ByRef 真得没有任何意义,因此, 与令人费解的 ByRef 参数相比,该语言对 ByRef 返回值的处理方式有所不同(而 Jared 现在得将更多的案例补充到那篇博文了)。那会给 VB 带来大量的困惑、语法和概念负担,因为 C#增加了一项某一天可能用于编写一个集合类型的特性。
相反,我们采用一种不同的方法。简而言之,我们仅仅实现消费场景所需的特性,让你使用 Slice(或者类似的返回 ref 的东西)就刚好可以完成你可以对数组进行的操作,除此之外,不提供任何额外的特性。当你使用索引指定一个数组元素时,你可以直接给它赋值,但你不能将数组元素的引用赋值给 ref 局部变量,并稍后再赋值给数组,你可以在那个索引指定的位置对值进行修改,你可以传递那个 ByRef 元素,但不能返回 ByRef。也就是说,没有新语法,VB 得到了保护。如果在 VS2017 发布之后,该语言的下一个版本发布之前,我们增加了 Slice,而它作为一个集合类型,成了所有集合类型的终结者,一夜之间出现了数百万计的 API 返回 ByRef 类型。如果这个可能性微乎其微的噩梦永远不会发生,那么该语言就不会受此损害。这就是为什么它要那样设计。
(感兴趣的读者可以通过上面的链接阅读完整解释)
VB 不是唯一一门这样做的语言。同样的考虑使得 C#没有包含对 XML 库的支持,许多 ASP.NET MVC 开发人员都会希望它有这个特性。
评论