C# Version 3.0 Specification
September 2005
翻译: 邱龙斌 <qiu_lb (at) hotmail.com>
2005-09-15
得益于互联网的开放性和专业人员的共享精神,过去几年里我在网络上搜索到很多重要的参考 资料和电子文档。在此对大家的奉献性的工作表示感谢。
近日无意中发现了 Microsoft 的 LINQ 项目,这个项目是用来试验 C#未来版本也就是 3.0 版本 的新功能的。有兴趣的朋友可以到 LINQ 项目主页去看看,上面有 C# 3.0 和 LINQ 的介绍、示 例代码。http://msdn.microsoft.com/netframework/future/linq/
本人使用 c++多年,深知语言核心的稳定和程序库的激进同样重要。对 c++而言 boost 提供了许 多库扩展方面的最佳实践,比如 boost.python,boost.function,boost.lambda 等。新的 c++0x 标准提案中提到在语言核心层直接支持 concept 和 model 的概念,从而在编译期进行 concept
和 model 的检查,就类型约束这点,我知道 c#2.0 泛型是用 where 表示泛型类型参数的约束的。
C#语言核心,近年来动作很大,继 2.0 加入泛型、匿名方法、迭代器、不完整类型、Nullable 类型之后,3.0 更是加入了一些引人注目的新特性。感慨之余,开发人员又要继续学习了;同 时开始担心例如 Mono,DotGnu 等开源.Net 项目。
浏览了一下 C# 3.0 Specification,感觉 C#有越来越动态化的倾向,数据查询方面也更直接。 花了点时间翻译成中文,希望对有需要的朋友有用。翻译错误再所难免,有问题的朋友可以跟 我联系,讨论本文的翻译问题。
声明:本译文不可用于商业目的流传, Microsoft 可能有异议。
Notice
© 2005 Microsoft Corporation.
All rights reserved.
Microsoft, Windows, Visual Basic, Visual C#, and Visual C++ are either registered trademarks or trademarks of
Microsoft
Corporation in
the U.S.A.
and/or other countries/regions.
Other product and
company names mentioned herein
may be the trademarks of their respective owners.
Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
Overview of C# 3.0
目录
26.C# 3.0 概述..................... ............. .............. ............. .............. .............. ............. .............. ............. .............. .......3
26.1 隐型局部变量(implicitly typed local variable)...........................................................................................3
26.2 扩展方法.......................................................................................................................................................4
26.2.1 声明扩展方法........................................................................................................................................4
26.2.2 导入扩展方法........................................................................................................................................4
26.2.3 扩展方法调用........................................................................................................................................5
26.3Lambda 表达式.............................................................................................................................................6
26.3.1Lambda 表达式转换...............................................................................................................................7
26.3.2 类型推导................................................................................................................................................8
26.3.3Overload resolution 重载决议..............................................................................................................10
26.4 对象和集合初始化器.................................................................................................................................10
26.4.1Object initializers 对象初始化器.........................................................................................................11
26.4.2 集合初始化器......................................................................................................................................13
26.5 匿名类型.....................................................................................................................................................14
26.6 隐型数组(Implicitly typed arrarys).......................................................................................................15
26.7 查询表达式.................................................................................................................................................16
26.7.1 查询表达式 translation........................................................................................................................17
26.7.1.1where 子句.....................................................................................................................................17
26.7.1.2select 子句......................................................................................................................................17
26.7.1.3group 子句......................................................................................................................................18
26.7.1.4orderby 子句...................................................................................................................................18
26.7.1.5 多重产生器...................................................................................................................................18
26.7.1.6info 子句.........................................................................................................................................19
26.7.2 查询表达式模式..................................................................................................................................19
26.7.3 正式的转换规则..................................................................................................................................20
26.8 表达式树(Expression trees)..................................................................................................................22
ii Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
26.C# 3.0 概述
C# 3.0 (“C# 魔兽(Orcas)”) 引入了几个构建在 C# 2.0 上的语言扩展,用来支持创建和使用更高级的函数 式(functional 或译:泛函)类库。这些扩展允许 组合(compositional)APIs 的构造,这些 APIs 与关系数据
库和 XML 等领域中的查询语言具有同等的表达力。
· 隐型局部变量,允许局部变量的类型从初始化它们的表达式推导而来。
· 扩展方法,使得使用附加(additional)的方法扩展已存在的类型和构造类型成为可能。
· Lambda 表达式,是匿名方法的演进,可提供改良的类型推导和到 dalegate 类型和表达式树的转换。
· 对象初始化器,简化了对象的构造和初始化。
· 匿名类型,是从对象初始化器自动推导和创建的元组(tuple)类型。
· 隐型数组,数组创建和初始化的形式,它从数组初始化器推导出数组的元素类型。
· 查询表达式,为类似于关系型和层次化查询语言(比如 SQL 和 XQuery)提供一个语言集成
(intergrated)的语法。
· 表达式树,允许 lambda 表达式表示为数据(表达式树)而不是代码(delegate)。 本文档是这些特征的技术概述。文档引用了 C#语言规范 1.2(§1-§18)和 C#语言规范 2.0(§19-§25),
这两个规范都在 C#语言主页上(http://msdn.microsoft.com/vcsharp/language)。
26.1 隐型局部变量(implicitly typed local variable)
在隐型局部变量声明中,正被声明的局部变量的类型从初始化这个变量的表达式推导得来。当局部变
量声明指明 var 作为类型,并且该范围域(scope)中没有 var 名称的类型存在,这个声明就称为隐型局部
声明。例如:
var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
上面的隐型局部变量声明精确地等同于下面的显型(explicitly typed)声明:
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
隐型局部变量声明中的局部变量声明符(declarator)遵从下面这些约束:
· 声明符必须包含初始化器。
· 初始化器必须是一个表达式。初始化器不能是一个自身的对象或者集合初始化器(§)),但是它可以 是包含一个对象或集合初始化器的一个 new 表达式。
· 初始化器表达式的编译期类型不可以是空(null)类型。
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 3
Overview of C# 3.0
· 如果局部变量声明包含了多个声明符,这些声明符必须具备同样的编译期类型。 下面是一些不正确的隐型局部变量声明的例子:
var x; // Error, no initializer to infer type from
var y = {1, 2, 3}; // Error, collection initializer not permitted var z = null; // Error, null type not permitted
因为向后兼容的原因,当局部变量声明指定 var 作为类型,而范围域中又存在叫 var 的类型,则这个声 明会推导为那个叫 var 的类型;然后,会产生一个关注含糊性(ambiguity)的警告,因为叫 var 的类型违 反了既定的类名首字母大写的约定,这个情形也未必会出现。(译者:视编译器实现而定)
for 表达式(§8.8.3)的 for 初始化器(for-initializer) 和 using 表达式的资源获取(resource-acquisition)可以作为 一个隐型局部变量声明。同样,foreach 表达式(§8.8.4)的迭代变量可以声明为一个隐型局部变量,这种 情况下,(隐型局部变量的)类型推导为正被枚举(enumerated)的集合的元素的类型。例子:
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
n 的类型推导为 numbers 的元素类型 int。
26.2 扩展方法
扩展方法 是可以通过使用实例方法语法调用的静态方法。效果上,扩展方法使得用附加的方法扩展已 存在类型和构造类型成为可能。
注意
扩展方法不容易被发现并且在功能上比实例方法更受限。由于这些原因,推荐保守地使用和仅在实例方法不可行 或不可能的情况下使用。
其它种类的扩展方法,比如属性、事件和操作符,正在被考虑当中,但是当前并不被支持。
26.2.1 声明扩展方法
扩展方法是通过指定关键字 this 修饰方法的第一个参数而声明的。扩展方法仅可声明在静态类中。下面 是声明了两个扩展方法的静态类的例子:
namespace Acme.Utilities
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length – index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
扩展方法具备所有常规静态方法的所有能力。另外,一旦被导入,扩展方法可以使用实例方法语法调 用之。
26.2.2 导入扩展方法
扩展方法用 using-namespace-directives (§9.3.2)导入。除了导入包含在名字空间中的类型外,using-
namespace-directives 也导入了名字空间中所有静态类中的所有扩展方法。实际上,被导入的扩展方法作
4 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
为被修饰的第一个参数类型上的附加方法出现,并且相比常规实例方法具有较低的优先权。比如,当 使用 using-namespace-directive 导入上个例子中 Acme.Utilities 名字空间:
using Acme.Utilities;
它使得可以在静态类 Extension 上使用实例方法语法调用扩展方法:
string s = "1234";
int i = s.ToInt32(); // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // Same as Extensions.Slice(digits, 4, 3)
26.2.3 扩展方法调用
扩展方法调用的详细规则表述如下。以如下调用形式之一:
expr . identifier
( )
expr . identifier
( args )
expr . identifier
< typeargs > ( )
expr . identifier
< typeargs > ( args )
如果调用的正常处理过程发现没有适用的实例方法(特别地,如果这个调用的候选方法集是空的), 就会试图处理扩展方法调用的构造。方法调用会首先被分别重写称如下之一:
identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )
重写后的形式然后被作为静态方法调用处理,除非标识符 identifier 决议为:以最靠近的封闭名字空间 声明开始,以每个封闭名字空间声明继续,并以包含的编译单元结束,持续地试图用组成所有可访问
的,由 using-namespace-directives 导入的,指明为 identifier 名字的扩展方法 处理重写的方法调用。第一 个产生非空候选方法集的方法组(method group)就成为被选中的重写的方法调用。如果所有的努力都只 产生空的候选集,则发生编译期错误。
前面的规则标表明实例方法优先于扩展方法,并且导入进内层名字空间中的扩展方法优先于导入进外 层名字空间中的扩展方法。例如:
using N1;
namespace N1
{
public static class E
{
public static void F(this object obj, int i) { }
public static void F(this object obj, string s) { }
}
}
class A { }
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 5
Overview of C# 3.0
class B
{
public void F(int i) { }
}
class C
{
public void F(object obj) { }
}
class X
{
static void Test(A a, B b, C c) {
a.F(1); // E.F(object, int)
a.F("hello"); // E.F(object, string)
b.F(1); // B.F(int)
b.F("hello"); // E.F(object, string)
c.F(1); // C.F(object)
c.F("hello"); // C.F(object)
}
}
例子中,B 的方法优先于第一个扩展方法,C 的方法优先于两个扩展方法。
26.3 Lambda 表达式
C# 2.0 引入了匿名方法,它允许在 delegate 值(delegate value) (译者:delegate 对象)被需要的地方以内联
(in-line)方式写一个代码块。当匿名方法提供了大量函数式编程语言(或泛函编程)(functional programming)的表达力时,实质上,匿名方法的语法是相当烦琐和带有强制性的。Lambda 表达式提供 了一个更加简练的函数式语法来写匿名方法。
Lambda 表达式写成一个后面紧跟 => 标记的参数列表,=>之后是一个表达式或表语句块。
expression:
assignment
non-assignment-expression
non-assignment-expression: conditional-expression lambda-expression
query-expression
lambda-expression:
( lambda-parameter-listopt ) => lambda-expression-body implicitly-typed-lambda-parameter => lambda-expression-body
lambda-parameter-list:
explicitly-typed-lambda-parameter-list implicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter-list explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter-list , explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter:
parameter-modifieropt type identifier
6 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
implicitly-typed-lambda-parameter-list implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter-list , implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter:
identifier
lambda-expression-body:
expression block
Lambda 表达式的参数可以是显型和隐型的。在显型参数列表中,每个参数的类型是显式指定的。在隐 型参数列表中,参数的类型由 lambda 表达式出现的语境推导——特定地,当 lambda 表达式被转型到一 个兼容的 delegate 类型时,delegate 类型提供参数的类型(§)。
在有单一的隐型参数的 lambda 表达式中,圆括号可以从参数列表中省略。换句话说,如下形式的
lambda 表达式
( param ) => expr
可以被简写成
param => expr
下面是一些 lambda 表达式的例子:
x => x + 1 // Implicitly typed, expression body
x => { return x + 1; } // Implicitly typed, statement body
(int x) => x + 1 // Explicitly typed, expression body
(int x) => { return x + 1; } // Explicitly typed, statement body
(x, y) => x * y // Multiple parameters
() => Console.WriteLine() // No parameters
通常,C# 2.0 规范§21 中提供的匿名方法规范,也应用上了 lambda 表达式。Lambda 表达式是匿名方法 的泛函超集,它提供了如下附加功能:
· Lambda 表达式允许参数类型被省略掉和被推导,尽管匿名方法要求显式指定参数类型。
· Lambda 表达式体可以是一个表达式或者语句块,尽管匿名方法体可以是一个语句块。
· Lambda 表达式作为参数传递参与类型参数推导(§26.3.3)和重载决议。
· 带有表达式体的 Lambda 表达式可以被转换成表达式树(§26.8)。
注意
PDC 2005 技术预览编译器不支持带有语句体的 lambda 表达式。在需要语句体的情况下,必须使用 C# 2.0 匿名方 法语法。
26.3.1 Lambda 表达式转换
与匿名方法表达式(anonymous-method-expression)类似,lambda 表达式是用特殊转换规则作为值(value) 类型分类的。这个值(value)没有类型,但是可以隐式转型至一个兼容的 delegate 类型。特别地,delegate
类型 D 与 lambda 表达式 L 兼容的,如果:
· D 和 L 有相同数目的参数。
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 7
Overview of C# 3.0
· 如果 L 有显型参数列表,D 中的每个参数有着与相应的 L 中的参数相同的类型和修饰符。
· 如果 L 有隐型参数列表,D 不可有 ref 或 out 参数。
· 如果 D 有 void 返回类型,并且 L 的体(body)是一个表达式,当 L 的每个参数被给定为对应的 D 中参 数的类型时,L 的体是一个允许作为语句-表达式(statement-expression(§8.6))的有效表达式
· 如果 D 有 void 返回类型并且 L 的体是语句块,当 L 的每个参数类型是被给定为相应的 D 参数的类 型时,L 的体是一个没有返回语句的有效语句块。
· 如果 D 有 non-void 返回值并且 L 的体是一个表达式,当 L 的每个参数类型是被给定的相应于 D 参数 的类型时,L 的体是一个可以隐式转换到 D 返回类型的有效表达式。
· 如果 D 有 non-void 返回值并且 L 的体是一个语句块,当 L 的每个参数类型是被给定的相应于 D 参数 的类型时,L 的体是一个有效的语句块,语句块中有不可到达(non-reachable)的终点(end point)(译者: 是否应该为“没有不可到达的终点”),且每个终点的返回语句指明一个可以隐式转换到 D 返回类 型的表达式。
下面的例子使用泛型 delegagte 类型 Func<A,R>表示一个带有参数类型 A 和返回类型 R 的函数:
delegate R Func<A,R>(A arg);
赋值如下:
Func<int,int> f1 = x => x + 1; // Ok Func<int,double> f2 = x => x + 1; // Ok Func<double,int> f3 = x => x + 1; // Error
每个 Lambda 表达式的参数和返回类型决定于 lambda 表达式被赋值的变量的类型。第一个赋值成功地 转换 lambda 表达式到 delegate 类型 Func<int,int>,是因为当 x 是 int 型,x+1 是一个有效的表达式并可 以隐式地转换到类型 int。同样第二个赋值成功地转换 lambda 表达式到 delegate 类型 Func<int,double>, 是因为 x+1 的返回值(类型 int)是隐式转换成 double 的。然而第三个赋值有编译期错误,因为当 x 是
double,x+1 是 double,不能够隐式转变到类型 int。
26.3.2 类型推导
当泛型方法被调用而不指明类型参数时,参数推导过程试图从调用中推导出类型参数。Lambda 表达式 参数传递给泛型方法参与这个类型推导过程。
如同§20.6.4 中表述的那样,类型推导首先为每个参数独立的发生。在初始阶段,不能从 lambda 表达式 参数推导出任何东西。然而,初始阶段之后,产生了使用迭代过程的额外的推导。特别地,只要有一 个或多个满足如下条件为真的参数存在,推导将会产生:
· 参数是 lambda 表达式,下面称为 L,从中,尚无推导。
· 相应的参数类型,下面称为 P,是有返回类型的含有一个或多个方法类型参数的 delegate。
· P 和 L 拥有相同数目的参数,并且 P 中的每个参数与 L 中相应的参数具有相同的修饰符,或者如果 L 有隐型参 数列表时,没有修饰符。
· P 的参数类型不包含方法类型参数或者包含仅仅一个方法类型参数,对这个参数已经产生一个相容的推导集。
· 如果 L 有一个显型参数列表,当推导出的类型对于 P 中的方法类型参数是可替换的时候,P 中的每
个参数拥有与 L 中对应的参数相同的类型。
· 如果 L 有一个隐型参数列表,当推导出的类型对于 P 中的方法类型参数是可替代的,并且返回参数 类型被给予 L 的参数,L 的体是一个有效表达式或语句块。
8