【IT168技术文档】
大约20天前曾经写了一篇C#3.0学习笔记,今天读了MSDN关于LINQ项目的介绍,很有收获。
文章(“LINQ 项目-.NET 语言集成查询”)是Dan Box和Anders Hejlsberg写的,内容很多,而且不是以C#3.0的新功能为主线组织的,只在“支持 LINQ 项目的语言功能”节中从LINQ的需求角度点评了那些新功能。现在在原笔记的基础上附上文中的这些点评,后补充的文字用粗体以示区别。
实际上有一篇主题更明确、更易懂的文章:LINQ 的演变及其对 C# 设计的影响,从对C#的期望出发,逐渐揭示了3.0中主要新特性的登台理由。
在探究当前的和下一代技术时,明显可以看出,有关编程技术的下一个难题是降低访问和集成特定信息(这些信息不是使用 OO 技术进行原始定义的)的复杂性。非 OO 信息的两个最常见源是关系数据库和 XML。
对于 LINQ 项目,我们采取了更为普通的方法,并向 .NET Framework 中添加了适用于所有信息源(而不只是关系数据或 XML 数据)的通用查询工具,而不是在编程语言和运行库中添加相关功能或特定于 XML 的功能。该工具名为 .NET 语言集成查询 (LINQ)。
1. Implicitly typed local variables
In an implicitly typed local variable declaration, the type of the local variable being declared is inferred from the expression used to initialize the variable.
C#始终是强类型的,var关键字做的只是表面文章。无论如何都要保证,编译器仅根据当前的声明语句(实际上可供它参考的信息就是初始化表达式)就能推断变量类型。
要使变量能够引用匿名类型的实例,同时仍然从静态类型获益,C# 引入了 var 关键字,以便用于替换局部变量声明的类型名称。
var 关键字方便用于其类型名称有意义的变量,但对于引用匿名类型实例的变量而言是必需的。
2. Extension methods
Extension methods are static methods that can be invoked using instance method syntax. In effect, extension methods make it possible to extend existing types and constructed types with additional methods.
之前,我们为类定义一个实例方法,编译器将之实现为一个静态函数,它以当前类的实例为第一个参数。现在,我们在静态类中定义这么一个静态方法,它的第一个参数前有this修饰符,于是编译器假装它是第一个参数所在类型的实例方法。
联想到曾有人用C语言写出“非常面向对象”的代码,再一次觉得高级的语言功能说到底就是编译器玩的把戏。
这个功能允许你在不修改类的定义的前提下,为它增添新的方法。就效果而言,它多少体现了基于原型的面向对象语言(prototype-based object-oriented language)的特点。比如在javascript中,可以写这样的代码:
不同之处在于,C#是根据参数类型自动把静态函数关联到类型的实例方法上的,而在javascript中需要显式地指定对象原型的属性。//定义一个函数,函数内部使用了this关键字。 function hi() { alert("Hi there! this is " + this + "."); } String.prototype.sayHi= hi; //为String对象添加sayHi方法,并指定其实现由函数hi定义。var p= new String("pzy"); p.sayHi();
标准查询操作符将定义为 System.Query.Sequence 类型的扩展方法。……希望将标准查询操作符替换为特定类型的用户可以:(a) 使用兼容的签名在特定类型上定义他们自己的同名方法,或者 (b) 定义可扩展特定类型的新的同名扩展方法。
3. Lambda expressions
Lambda expressions provide a more concise, functional syntax for writing anonymous methods.
考虑定义一个Foreach函数。在C#中,代码可能是这样的:
呵呵,C#2.0中定义匿名方法的语法与javascript的几乎一样,而C#3.0的语法则简短到骇人的程度了!delegate void Action(A a);static void Foreach(IEnumerable collection, Action action) { foreach (T t in collection) action(t); } string[] words = new string[] { "Are", "You", "OK", "?" }; //C#2.0: Foreach(words, delegate(string w) { Console.WriteLine(w); }); //C#3.0: Foreach(words, w=> Console.WriteLine(w));
基于 λ 表达式的概念而生成的查询工具为开发人员提供了一种编写函数的简便方法,这些函数可以作为后续计算的参数进行传递。
4. Object and collection initializers
An object creation expression (§7.5.10.1) may include an object or collection initializer which initializes the members of the newly created object or the elements of the newly created collection.
这么直观的语法早该有了,简便程度上直逼javascript啊。不过javascript走得更远,它的数组不但可以用元素列表初始化,甚至还有字面值(literal value),比如
对象初始化表达式是语言集成查询的一个重要功能,因为它们允许在仅允许表达式的上下文(如 λ 表达式和表达式树)中生成新的结构化值。coffees = ["French Roast", "Columbian", "Kona"];//这不是初始化,而是赋值。
5. Anonymous types
C# 3.0 permits the new operator to be used with an anonymous object initializer to create an object of an anonymous type.
霍霍,implicitly typed local variables + object initializer = anonymous types!
LINQ 项目支持以数据为中心的编程样式,其中,某些类型的存在主要是为了通过结构化值提供静态“形式”,而不是提供同时具有状态和行为的完整对象。……匿名类型允许将新的结构定义为与它们的初始化进行“内联”。
6. Implicitly typed arrays
The syntax of array creation expressions (§7.5.10.2) is extended to support implicitly typed array creation expressions.
implicitly typed local variables + collection initializer = Implicitly typed arrays
7. Query expressions
Query expressions provide a language integrated syntax for queries that is similar to relational and hierarchical query languages such as SQL and XQuery.
The C# 3.0 language does not specify the exact execution semantics of query expressions. Rather, C# 3.0 translates query expressions into invocations of methods that adhere to the query expression pattern. Specifically, query expressions are translated into invocations of methods named Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy, and Cast that are expected to have particular signatures and result types.
C#引入了很多语言构造来支持特定的操作。比如foreach语句、using语句等,在支持函数回调的语言中,这些语句的功能都可以用函数来模拟,C++ STL中就有for_each函数。
C#3.0的Query expressions应该也是这类语言构造。乍一看,貌似可以在C#中写类似SQL的查询语句来从对象中提取信息了——把查询语句写在字符串中并不稀奇,C#3.0的厉害之处在于,它把查询语句提升为语言级别的构造。至于实现原理,似乎与C语言的宏有神似之处,编译器在类型绑定和重载决策之前,把查询表达式翻译为相应的方法调用。比如下面的查询表达式
将被翻译为from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }
显然,为了支持查询表达式,customers所在的类型必须包含GroupBy,Select等方法(pzy:这种方法称为“查询操作符”,引入扩展方法的主要意图就是让用户可以定义查询操作符。)的定义。customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
从查询表达式到方法调用的翻译,是在语法层面上,于是我感觉,之前的一堆功能,动机都是简化、统一、兼容语法,它们在一定程度上是为查询表达式服务的。比如扩展方法,就是为了能够以与实例方法相同的语法调用在其它类中定义的静态方法。
与 foreach 语句一样,查询语法是一个方便的声明性代码缩写,您可以手动编写它。
8. Expression trees
Expression trees permit lambda expressions to be represented as data structures instead of executable code.
把代码封装为数据?还没领会它的真正意图是什么,好像也是为查询表达式服务的。
λ 表达式的优点是,能够提供最直接而简洁的创作语法。更重要的是,λ 表达式可以编译为代码,也可以编译为数据,从而允许优化器、转换器和计算器在运行时处理 λ 表达式。表达式树是 λ 表达式的有效内存中数据表示形式,它使表达式的结构透明且显式。
编译器是发出可执行 IL 还是表达式树取决于 λ 表达式的用法。如果将 λ 表达式指定给委托类型的变量、字段或参数,则编译器将发出与匿名方法等效的 IL。如果将 λ 表达式指定给 Expression 类型的变量、字段或参数,则编译器将发出表达式树。
例如,请考虑以下两个变量声明:
变量 f 是对委托的引用,可以直接执行:Func f = n => n < 5; Expression e = n => n < 5;
bool isSmall = f(2); // isSmall is now true
变量 e 是对表达式树的引用,不可直接执行:bool isSmall = f(2); // isSmall is now true
与委托(有效的不透明代码)不同,我们可以像与程序中的任何其他数据结构交互那样与表达式树进行交互。例如,以下程序:bool isSmall = e(2); // compile error, expressions == data
在运行时分解表达式树,并显示以下字符串:Expression filter = n => n < 5; BinaryExpression body = (BinaryExpression)filter.Body; ParameterExpression left = (ParameterExpression)body.Left; ConstantExpression right = (ConstantExpression)body.Right; Console.WriteLine("{0} {1} {2}", left.Name, body.NodeType, right.Value);
n LT 5