C# Version 3.0 Specification
September 2005
翻译: 邱龙斌 <qiu_lb (at) hotmail.com>
得益于互联网的开放性和专业人员的共享精神,过去几年里我在网络上搜索到很多重要的参考 资料和电子文档。在此对大家的奉献性的工作表示感谢。
近日无意中发现了 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
Other product and company names mentioned herein may be the trademarks of their respective owners.
|
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 表达式(§
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 (§
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 表达式 参数传递给泛型方法参与这个类型推导过程。
如同§
· 参数是 lambda 表达式,下面称为 L,从中,尚无推导。
· 相应的参数类型,下面称为 P,是有返回类型的含有一个或多个方法类型参数的 delegate。
· P 和 L 拥有相同数目的参数,并且 P 中的每个参数与 L 中相应的参数具有相同的修饰符,或者如果 L 有隐型参 数列表时,没有修饰符。
· P 的参数类型不包含方法类型参数或者包含仅仅一个方法类型参数,对这个参数已经产生一个相容的推导集。
· 如果 L 有一个显型参数列表,当推导出的类型对于 P 中的方法类型参数是可替换的时候,P 中的每
个参数拥有与 L 中对应的参数相同的类型。
· 如果 L 有一个隐型参数列表,当推导出的类型对于 P 中的方法类型参数是可替代的,并且返回参数 类型被给予 L 的参数,L 的体是一个有效表达式或语句块。
8 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
· 返回类型可以为 L 推导出来,描述如下:
对每一个这样的参数,将会通过关联 P 的返回类型和 L 的推导返回类型做出推论,并且新的推论被加入 进累积的推论集。这个过程将重复进行,直到没有更进一步的推论产生为止。
因为类型推导和重载决议的原因,lambda 表达式 L 推导出的类型决定于下面:
· 如果 L 的体是一个表达式,表达式的类型就是推导出的 L 的返回类型。
· 如果 L 的体是一个语句块,如果由语句块中 return 语句表达式的类型形成的集合(set)正好包含一个 集合中每个类型都可隐式转换成的类型,那么这个类型就是推导出的 L 的返回类型。(译者:如果有 个集合{int, byte, double},则 double 满足要求)
· 此外,返回类型不能为 L 推导出来。
作为一个包含 lambda 表达式的类型推导的例子,考虑声明于 System.Query.Sequence 类中的 Select 扩展 方法:
namespace System.Query
{
public static class Sequence
{
public static IEnumerable<S> Select<T,S>(
this IEnumerable<T> source,
Func<T,S> selector)
{
foreach (T element in source) yield return selector(element);
}
}
}
假定 System.Query 名字空间使用 using 子句导入,并且给出一个类 Customer,带有类型为 string 的属性
Name, Select 方法可用作选择一列(list of )customers 的名字
List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);
Select 扩展方法调用通过重写静态方法调用处理:
IEnumerable<string> names = Sequence.Select(customers, c => c.Name);
因为类型参数未被显式指明,将会使用类型推导来推导类型参数。首先 customers 参数被关联到 source 参数,推导 T 是 Customer。然后使用前面描述的 lambda 表达式类型推导过程, c 是给定类型 Customer, 而表达式 c.Name 被关联到 selector 参数的返回类型上,推导 s 是 string,这样,调用就等价于
Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)
返回类型是 IEnumerable<string>。
下面的例子示范了 lambda 表达式类型推导是如何允许类型信息在泛型函数调用的参数之间“流动”的。
给出方法
static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
return f2(f1(value));
}
调用的类型推导
double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 9
Overview of C# 3.0
处理过程如下:首先参数”1:15:
数 s 是给定推导类型 string,并且表达式 TimeSpan.Parse(s)被关联到 f1 的返回类型上,推导 Y 为 System.TimeSpan。最后第二个 lambda 表达式的参数 t,是给定为推导类型 System.TimeSpan,表达式 t.ToTalSeconds 被关联到 f2 的返回类型上,推导 Z 为 double。这样,调用的返回类型就是 double。
26.3.3 Overload resolution 重载决议
参数列表中的 Lambda 表达式在某些条件下影响重载决议。
下面的规则要增加进§
类型 D1 和 D2 具有相同的参数列表,从 L 到 D1 的隐式转型比从 L 到 D2 的隐式转型更好;并且从 L 推导出 的返回类型到 D1 返回类型的隐式转型比从 L 推导出的返回类型到 D2 返回类型的隐式转型更好。如果这 些条件不为真,两者都不行。
下面的例子例示了这个规则的效果。
class ItemList<T>: List<T>
{
public int Sum<T>(Func<T,int> selector) {
int sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
public double Sum<T>(Func<T,double> selector) {
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
}
ItemList<T>类有两个 Sum 方法。每个方法都有一个 selector 参数,方法从列表项中提取值累加进 sum。 提取的值可以是 int 或 double 型,返回 sum 同样可以是 int 或 double 型。
Sum 方法可以作为例子用于从 detail 列表中依次计算和。
class Detail
{
public int UnitCount;
public double UnitPrice;
...
}
void ComputeSums() {
ItemList<Detail> orderDetails = GetOrderDetails(...);
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
...
}
orderDetails.Sum 首次调用中,两个 Sum 方法都适用,这是因为 lambda 表达式 d=>d.UnitCount 兼容于
Func<Detail,int>和 Func<Detail,double>这两者。然而,重载决议选择了第一个 Sum 方法,这是因为转换
至 Func<Detail,in>好于转换至 Func<Detail,double>。
orderDetails.Sum 的第二次调用中,仅仅第二个 Sum 方法适用,这是因为 lambda 表达式
d=>d.UnitPrice*d.UnitCount 产生的类型是 double。因此重载决议为此调用选择了第二个方法。
26.4 对象和集合初始化器
对象创建表达式(§
10 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
object-creation-expression:
new type ( argument-listopt ) object-or-collection-initializeropt
new type object-or-collection-initializer
object-or-collection-initializer:
object-initializer collection-initializer
对象创建表达式可以省略构造函数(译者:或译“构造器”)(constructor)的参数列表和封闭的圆括号,而 提供给它一个对象或集合初始化器。省略构造函数参数列表和封闭的圆括号等价于指定一个空参数列
表。
包含对象或集合初始化器的对象创建表达式的执行包含首先调用实例构造函数,然后执行由对象或集
合初始化器指定的成员或元素初始化动作。
对象或集合初始化器不能引用正被实例化的对象实例。
26.4.1 Object initializers 对象初始化器
对象初始化器指定一个或多个对象的域或属性的 值。
object-initializer:
{ member-initializer-listopt }
{ member-initializer-list , }
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer
对象初始化器由一系列成员初始化器组成,封闭于{和}标记内并且由逗号间隔。每个成员初始化器必须 指出正被初始化的对象的域或属性的名字,后面是等号”=”和 表达式或者对象或集合的初始化器。
在等号后面指定表达式的成员初始化器作为与对域或属性赋值同样的方式处理。 在等号后指定一个对象初始化器的成员初始化器是对内嵌对象的初始化。对象初始化器中的赋值作为
域或属性成员的赋值对待,而不是给域或属性赋予新值。值类型的属性不可用这种构造方式初始化。
在等号后指定集合初始化器的成员初始化器是对内嵌集合的初始化。初始化器中给定的元素被加进域或
属性引用的集合中,而不是给域或属性赋予新的集合。域或属性必须是满足§中指定要求的集合类型。
下面的类要求一个有两个坐标的 point:
public class Point
{
int x, y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
Point 的实例可以被创建和实例化如下:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 11
Overview of C# 3.0
var a = new Point { X = 0, Y = 1 };
它等效于
var a = new Point();
a.X = 0;
a.Y = 1;
下面的这个类表示由两个 points 构成的 rectangle。
public class Rectangle
{
Point p1, p2;
public Point P1 { get { return p1; } set { p1 = value; } }
public Point P2 { get { return p2; } set { p2 = value; } }
}
Rectangle 的实例可以被创建和初始化如下:
var r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
它等效于
var r = new Rectangle();
var __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
r.P1 = __p1;
var __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
r.P2 = __p2;
这里__p1 和__p2 是临时变量且是不可见和不可访问的。
如果 Rectangle 的构造函数分配了两个内嵌的 Point 的实例
public class Rectangle
{
Point p1 = new Point(); Point p2 = new Point();
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}
下面的构造可被用于初始化内嵌的 Point 实例,而不是赋予新的实例值。
var r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
它等效于
var r = new Rectangle();
r.P1.X = 0; r.P1.Y = 1; r.P2.X = 2; r.P2.Y = 3;
12 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
26.4.2 集合初始化器
集合初始化器指定集合的元素。
collection-initializer:
{ element-initializer-listopt }
{ element-initializer-list , }
element-initializer-list:
element-initializer
element-initializer-list , element-initializer
element-initializer:
non-assignment-expression
集合初始化器由一系列元素初始化器组成,封闭进 { 和 } 标记内,以逗号间隔。每个元素初始化器指 定一个将被加进正被初始化的集合对象中的元素。
下面是对象创建表达式的例子,包含有一个集合初始化器:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
被应用了集合初始化器的集合对象必须是实现了正好一个类型 T 的 System.Collections.Generic.IConlection<T>的类型。此外必须存在从每个元素类型到 T 类型的隐式转型。 如果这些条件都不满足,就产生编译期错误。集合初始化器对每个指定元素依次调用 ICollection<T>.Add(T)方法。
下面的类表示一个名字和电话号码列表的 contact。
public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();
public string Name { get { return name; } set { name = value; } }
public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
List<Contact>可以被创建和实例化如下:
var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};
它等效于:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 13
Overview of C# 3.0
var contacts = new List<Contact>();
var __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
var __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);
这里__c1 和__c2 是临时变量,不可见,也不可访问。
26.5 匿名类型
C# 3.0 允许 new 操作符与匿名对象初始化器联用来创建一个匿名类型的对象。
primary-no-array-creation-expression:
…
anonymous-object-creation-expression
anonymous-object-creation-expression:
new anonymous-object-initializer
anonymous-object-initializer:
{ member-declarator-listopt }
{ member-declarator-list , }
member-declarator-list:
member-declarator
member-declarator-list , member-declarator
member-declarator: simple-name member-access
identifier = expression
匿名对象初始化器声明一个匿名类型并返回这个类型的实例。一个匿名类型是一个无名类(nameless class)(译者:参考 jjhou 先生的翻译“具名(named)”),它直接继承自 Object。匿名类型的成员是一系 列推导自用于创建这个类型实例的对象初始化器的读/写属性。特别地,匿名对象初始化器具有如下形
式:
new { p1 = e1 , p2 = e2 , … pn = en }
它声明了一个如下形式的匿名类型
class __Anonymous1
{
private T
private T2
|
f2
|
;
|
…
|
|
|
private Tn
|
fn
|
;
|
|
public
|
T1
|
p1
|
{
|
get
|
{
|
return
|
f1
|
;
|
}
|
set
|
{
|
f1
|
=
|
value
|
;
|
}
|
}
|
|
public
|
T2
|
p2
|
{
|
get
|
{
|
return
|
f2
|
;
|
}
|
set
|
{
|
f2
|
=
|
value
|
;
|
}
|
}
|
|
…
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public
|
T1
|
p1
|
{
|
get
|
{
|
return
|
f1
|
;
|
}
|
set
|
{
|
f1
|
=
|
value
|
;
|
}
|
}
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这里每个 Tx 是对应表达式 ex 的类型。匿名对象初始化器中的表达式是 null 类型是一个编译期错误。
14 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
匿名类型的名字是由编译器自动产生的,在程序正文中不可被引用。
在同样的程序中,以相同顺序指定了一系列相同名字和类型的两个匿名对象初始化器将会产生相同匿 名类型的实例。(这个定义包含了属性的次序,是因为它在某些环境中这是可观测和重要的,比如 reflection)
例子
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
最后一行的赋值是可行的,因为 p1 和 p2 具有相同的匿名类型。
成员声明符可以缩写成简单的名字(§