【51CTO精选译文】.NET Framework的每一个新版本都给我们来带许多让.NET变得更强大和易用的新特性,.NET 4.0版当然也不例外。当我们关注一个个单独的新特性时,就会看到微软为兑现“联合发展”的诺言,正在C#和VB.NET之间相互取长补短。
动态查询(Dynamic Lookup)(C#中新引入)
前面我们提到过,C#新增了一个叫做动态(dynamic)的全新静态类型。虽然我们很多情况下它都能运行,但是并不太常用。你可以把动态类型当成一种支持延迟绑定的对象。
- dynamic Car = GetCar(); //Get a reference to the dynamic object
- Car.Model = "Mondeo"; //Assign a value to a property (also works for fields)
- Car.StartEngine(); //Calling a method
- Car.Accelerate(100); //Call a methods with parameters
- dynamic Person = Car["Driver"]; //Getting with an indexer
- Car["Passenger"] = CreatePassenger(); //Setting with an indexer
在编译时,对动态对象的字段、属性以及方法都基本上被忽略了。也就是说,即使某个成员不可用,在编译时也不会提示编译错误。因为那些信息时只有在运行时才会可用,.NET 知道如何使用DLR来解析动态成员。C#至今为止仍然是一门静态类型语言,就是因为性能上的考虑。新提供的这种动态类型并不就意味着你可以完全抛弃静态类型,它只是你不得不使用动态类型时可以用的一个工具。另外别忘了,VB .NET中已经支持动态查询(Dynamic Lookup)了。更多有关C# 4.0中的动态类型的使用方法,可参考51CTO之前发布的在Visual Studio 2010中使用C# 4.0的动态类型一文。
命名和可选参数(Named and Optional Parameters)(C#中新引入)
命名和可选参数已出现在VB.NET相当长的一段时间,现在C#终于也支持了。顾名思义,可选参数是你可以选择性地向方法或者构造函数传递参数。如果你的选择是不传递参数,那么被调用的这个方法就会使用之前定义的默认值。在C#中,要把一个方法的参数变成可选参数,只需要赋给它一个默认值就可以了。
- public void CreateBook(string title="No Title", int pageCount=0, string isbn = "0-00000-000-0")
- {
- this.Title = title;
- this.PageCount = pageCount;
- this.ISBN = isbn;
- }
你可以用下面这几种形式调用上面定义的CreateBook方法
- CreateBook();
- CreateBook("Some Book Title");
- CreateBook("Some Book Title", 600);
- CreateBook("Some Book Title", 600, "5-55555-555-5");
这里要注意的是可靠参数的位置是很重要的。在这个例子中,title必须是以第一个参数出现,page count作为第二个,然后ISBN作为第三个。如果你想调用CreateBook ,但只传递ISBN号码这个参数,那么你有两种方案可以实现这点。第一种方案是创建一个以ISBN为参数的重载方法,这种方法是很经典的办法,但是也是较为繁冗的。第二种方案是使用命名参数(named parameter),这种方案比前一种要简洁得多。命名参数可以让你以任何顺序传递参数,你只要提供参数的名字就可以了。这时,你可以用以下几种形式调用方法:
- CreateBook(isbn: "5-55555-5555-5");
- CreateBook("Book Title", isbn: "5-55555-5555-5");
- CreateBook(isbn: "5-55555-5555-5", title: "Book Title", pageCount: 600);
请注意,最开始你可以使用位置参数(positional parameter),然后再像用我们上面演示的第二种方法,但是如果你用上了命名参数(named parameter),那就必须得一直用下去。
动态导入(Dynamic Import)(C#中新引入 )
几乎所有通过COM API暴露出的接口都是使用可变数据类型,以前在C#中这是用数据类型对象表示的。在此之前,C#中是没有办法处理动态类型的,所以对这些类型的处理就变成了各种各样数据类型中的相互转换。不过现在C#中开始支持动态类型,你就可以把COM组件当作动态对象导入了,这样就可以不用显式转换对象类型而直接设置属性、调用方法了。
省略引用参数(Ref Parameter)(C#中新引入)
调用COM API所带来的另一个副产物就是,大量方法的参数必须通过引用传递。在大多数情况下,我们都只是想传一个值给方法,而不关心它返回的是什么。尽管如此,你依然需要创建许多临时变量来保存结果。这种单调乏味的工作可以交给实习生来做,让他们获得所谓“实际工作经验”。在C#4.0里,你可以向COM里直接传递参数值,编译器会自动帮你生成临时变量。从而节省开发人员时间,也让实习生丧失了很多所谓“实际工作经验”。
协变(Co-variance)和逆变(Contra-variance)(C#和VB .NET中新引入)
泛型(generics)中最惊人的一个问题已经在.NET 4.0中得到解决。以前,如果你有一个支持 IEnumerable < String>的对象 , 随后你想把它传递给一个需要 IEnumerable < object> 型参数的方法,你会发现这根本无法做到。你得生成一个新的支持 IEnumerable < object> 的对象,用从IEnumerable实例中获得的字符串填充它,然后再把它传递给方法。我们都知道,字符串是比对象更具体的类型,因此,我们理所当然地认为 List< string> 应该支持 IEnumerable < string> 接口和 IEnumerable < object> 。结果是,编译器并不会这样做。不过,在.NET 4.0里,这个问题已经得到了解决。因为现在泛型(generics)已经支持协变和异变。
协变和逆变都是关乎到程序的类型安全和性能的。粗略地说,协变表示可以认为某个对象具有弱派生性(less derived),只要在常规类型的参数前加上out关键字就表示协变了。协变类型被限制在输出位置中使用,也就是说它们只有在调用方法或者访问属性的结果里出现。这些就是协变类型能称得上”安全“的唯一地方,或者说是唯一一个在编译时不需要进行额外的类型检查的地方。在.NET4.0中 , IEnumerable < T> 接口也就等同于 IEnumerable < out T> 因为IEnumerable是协变的。这也意味着下面的例子是完全有效的:
- IEnumerable< string> strings = GetStrings();
- IEnumerable< object> objects = strings;
逆变(Contra-variance)表示可以认为某个对象具有强派生性(more derived),它可以通过在普通参数类型前加上in关键字修饰表示。逆变类型是限制在输入位置使用的,也就是说它只能出现在方法的参数中或者说必须是拥有”只写“属性。在.NET中4.0 中, IComparer < T> 接口现在变成了 IComparer < in T> ,因为IComparer是异变的。这个概念理解起来不太容易,但是领会了它们的含义之后能够免去泛型转换中的许多麻烦。有关C# 4.0中的协变和逆变,可参考51CTO之前发布的C# 4.0中泛型协变性和逆变性详解一文。
无需主互操作程序集(Primary Interop Assemblies)编译(C#和VB .NET中新引入)
主互操作程序集(Primary Interop Assemblies, PIA)是厂商提供的程序集,它处于COM组件和.NET Framework之间,其中最广为人知的是微软Office 主互操作程序集。在开发过程中,如果你的程序集里有对PIA的引用,那么就必须在布署程序集时附带上PIA,或者提供如何获得PIA的说明。C#和VB .NET的新特性允许你直接把PIA嵌入到自己的程序集中,从而大大简化布署。PIA往往比较大,所以把它整个包含进去可能会使得你的程序集臃肿很多。幸运的是,编译器会优化地选择只嵌入你实际上用到的那一部分PIA,这样在你只用到了PIA的一小部分时能有效减小PIA的点位面积(footprint)。
#p#
匿名方法的支持(VB.NET 中新引入)
VB.NET新引入的另一个特性就是是内置(inline)或匿名(anonymous)方法。匿名方法这个名称是非常贴切的,因为它允许你直接定义子方法(Subs)和函数,而不用另外在你的类里面再添加一个顶层(top-level)的方法,从而使这个方法隐藏起来(也就是匿名)。匿名方法还可以访问它所在代码块的所有可用变量,这样的话,定义匿名方法时甚至可以不需要用参数就可以实现数值的传入和返回。在现在通常使用AddressOf 关键字指向一个方法的地方你都可以定义一个匿名函数,所以它最大用处可能在于事件处理,如下例所示:
- Dim MyTimer As New System.Timers.Timer(1000)
- Dim Seconds As Integer = 0
- AddHandler MyTimer.Elapsed,
- Sub()
- Seconds += 1
- Console.WriteLine(Seconds.ToString() & " seconds have elapsed")
- End Sub
- MyTimer.Start()
- Console.WriteLine("Press any key to exit")
- Console.ReadLine()
注意对定义器的超时事件处理程序就是内嵌的,而且这个内嵌的方法还直接访问了在它之外定义的变量。您还可以定义内嵌函数:
- Dim f = Function(a As Integer, b As Integer)
- Return a + b
- End Function
- Dim x = 10
- Dim y = 20
- Dim z = f(x, y)
如果一个内嵌函数在代码块的上下文语境里有意义的话,那用起来确实是很方便,但是用了它之后很有可能会影响程序的重用性。
隐式续行(Implicit line continuation)(VB .NET中新引入)
看C#代码时,你一眼就可以看出来语句的末尾在哪里,因为它以分号作为语句的结束符。VB也有一个语句结束符,但是它的结束符是是回车,每个语句都被假设是同一行里。如果你打想打破这个规范,那就不得不使用下划线来显示表明下一行是这个语句的继续。写过VB .NET程序的人就应该会感觉到,这种方法既麻烦,又影响代码的美观。
- Dim text As String = "Wouldn't it be nice" & _
- "If you didn't have to" & _
- "put an underscore to" & _
- "continue to the next line?"
还好,现在我们再也不用这样了。VB.NET现在支持隐式结尾续行(implicit line continuation)。当编译器在某行发现一条不完整的语句时,它会自动检查下一行的内容是否包含语句的剩余部分。
- Dim text As String = "Wouldn't it be nice" &
- "If you didn't have to" &
- "put an underscore to" &
- "continue to the next line?" &
- "Sweet! Now you can!"
如果你还是喜欢怀旧的感觉,那也还是可以用原先的显示声明法,那种方法现在也还是可用的。并且有时候我们可以会不得不用它,因为编译器某些情况下可能无法判定下一行是不是续行。放心,这样的情况是不会经常出现的,而且如果发生这样的情况,编译器会通知你。
简化的属性语法(VB .NET新引入)
简化的属性语法是另一个从C#中引入VB .NET的特性。通常属性定义看起来是这样的:
- 'Field
- Private _name As String
- 'Property
- Public Property Name() As String
- Get
- Return _name
- End Get
- Set(ByVal value As String)
- _name = value
- End Set
- End Property
现在可以简写成:
- Public Property Name() as String
这把代码行数从9行减少到1行。如果你选择了简化的这种写法,那要注意的一个问题就是你无法访问存储它的值的那块区域,这在按引用传值时会带来问题。如果发生这种情况,您可以随时恢复到用通常的写法或使用一个临时变量。
数组类型判断(Array type inference)和多重数组(Jagged Arrays)(VB .NET新引入)
VB.NET现在支持数组类型判断和多重数组定义语法。这意味着你在带初始值定义时不用显式地声明它的类型,编译器能自动确定它的类型。例如:
- Dim Numbers = {1, 1, 2, 3, 5, 8, 13, 21, 34}
当你看到这个数组时,能很快确定它是整数型的,现在编译器就像我们一样能准确作出这个判断。
- Dim Numbers = {1, 1, 2, 3, 5.0, 8, 13, 21, 34}
当编译器看到上面这个例子时,它会发现5.0不是一个整型数,所以数组类型就是double型。类型判断也可以用于矩阵:
- Dim Names = {{"Sarah", "Jane", "Mary", "Susan", "Amanda"},
- {"Bob", "Joe", "Dustin", "Richard", "Nick"}}
编译器可以推断出string()是上面这个例子的类型。在多重数组中,你会遇到一些问题。你可以把一个二维矩阵当作一个每行的列数都相等的矩阵。多重数组每一行的列数则是可变的,所以它和矩阵还是有一些区别。你可能认为可以定义这样一个多重数组:
- Dim Names = {"Sarah", "Jane", "Mary", "Susan", "Amanda"},
- "Bob", "Nick"}
但是你会发现编译器抛出错误说”数组初始化时缺少三个元素“(Array initialiser is missing 3 elements), 这是因为编译器默认把它当成矩阵看待。如果你想定义一个多重数组,那么只需要把这些行用一对大括号括起来:
- Dim Names = {{"Sarah", "Jane", "Mary", "Susan", "Amanda"},
- {"Bob", "Nick"}}
现在编译器就能推断出来它的类型是string()(),这才是多重数组的正确类型。
From 关键字(VB.NET新引入 )
既然说到了初始化,那我们就不得不说说VB .NET中新引入的From关键字。当你创建一个字典、表格或者其它由许多对象组成的对象时,都通常是先创建好这个对象本身,然后再用合适的物品去填充它。现在,有了From关键字,就不用再反复去调用Add方法了,它能自动帮我们调用Add方法以填充列表。因此,现在不用像下面这样写了:
- Dim Colors As New List(Of String)
- Colors.Add("Red")
- Colors.Add("Green")
- Colors.Add("Blue")
只用缩减到这么一点就可以了:
- Dim Colors As New List(Of String) From {"Red", "Green", "Blue"}
毫无疑问,它实际上也是调用了Add方法,也就是说它能在任何包含Add方法的对象上起作用。事实上,你甚至可以使用扩展方法(extension method)创建一个Add方法或重载一个Add方法,如果传入的参数和方法声明相吻合,From关键字就会用到它们。在前面的示例中,List对象有一个只需要一个参数的Add方法,参数值就是你想要加入表格的那个字符串。如果你有一个带多个参数的Add方法,那传入参数时就可以像定义矩阵一样做。下面是一个例子,演示如何使用Add方法和一个Dictionary对象。
- Dim Colors2 As New Dictionary(Of String, String) From {
- {"Red", "FF0000"},
- {"Green", "00FF00"},
- {"Blue", "0000FF"}}
因为Dictionary的Add方法包含两个参数,键(key)和值(value),我们在From语句里传入参数时就必须两个两个地传入一组参数。另外,在使用From关键字时一定要注意保持可读性。某些特定情况下,你可能还是会想回到用Add方法。
【编辑推荐】