C# Version 3.0 Specification
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 具有相同的匿名类型。
成员声明符可以缩写成简单的名字(§
identifier expr
. identifier
分别等价于下面:
identifer = identifier identifier
= expr . identifier
因此,在投射初始化器中,identifier 选择了被赋予值的值和域或属性。直观上,投射初始化器不仅投射 值,也投射值的名字。
26.6 隐型数组(Implicitly typed arrarys)
扩展 数组创建表达式(§
array-creation-expression:
…
new [ ] array-initializer
在隐型数组创建表达式中,数组实例的类型推导自数组初始化器中元素的类型。特别地,数组初始化 器中表达式类型形成的类型集合(set),必须包含一个这样的类型,其中每个类型都可隐式转型成它,并 且这个类型不是 null 类型,如此,这个类型的数组就被创建了。如果不能推导出一个准确的类型,或者 推导出的类型是空 null 类型,编译器错误就会出现。
下面是隐型数组创建表达式的例子:
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[] { "hello", null, "world” }; // string[] var d = new[] { 1, "one", 2, "two" }; // Error
最后一个表达式导致编译器错误,这是因为 int 和 string 都不能隐式转换成对方。显型数组创建表达式 必须这么使用:例如指定类型为 object[]。另外,其中一个元素可以被转型到一个公共基类型,这个类 型就会成为推导出的元素类型。
隐型数组创建表达式可以与匿名对象初始化器结合使用来创建匿名类型的数据结构。例如:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 15
Overview of C# 3.0
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
26.7 查询表达式
查询表达式为查询提供了一个类似于关系和分层的查询语言(如 SQL 和 XQuery)的语言集成(译者:
intergrated 或译“整合”)语法。
query-expression:
from-clause query-body
from-clause:
from from-generators
from-generators:
from-generator
from-generators
, from-generator
from-generator:
identifier in expression
query-body:
from-or-where-clausesopt
orderby-clauseopt select-or-group-clause into-clauseopt
from-or-where-clauses:
from-or-where-clause
from-or-where-clauses from-or-where-clause
from-or-where-clause:
from-clause
where-clause
where-clause:
where boolean-expression
orderby-clause:
orderby ordering-clauses
ordering-clauses:
ordering-clause
ordering-clauses , ordering-clause
ordering-clause:
expression ordering-directionopt
ordering-direction: ascending descending
16 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
select-or-group-clause:
select-clause group-clause
select-clause:
select expression
group-clause:
group expression by expression
into-clause:
into identifier query-body
查询表达式作为非赋值(non-assignment-expression)表达式分类,其定义出现在§。
查询表达式以 from 开始,结束于 select 或 group 子句。开头的 from 子句可以跟随 0 个或者更多个 from
或 where 子句。每个 from 子句都是一个产生器,它引入了一个迭代变量在序列上搜索;每个 where 子 句是一个过滤器,它从结果中排除一些项。最后的 select 或 group 子句指定了依据迭代变量得出的结果 的外形(shape)。Select 或 group 子句前面可有一个 orderby 子句,它指明返回结果的顺序。最后 into 子句 可以通过把一条查询语句的结果作为产生器插进子序列查询中的方式来拼接查询。
在查询表达式中,多个产生器的 from 子句正好等价于多个连续的带有单个产生器的 from 子句。
26.7.1 查询表达式 translation
C# 3.0 语言没有指定查询表达式准确的执行语义。然而 C# 3.0 把查询表达式转换(translate)成遵循查询 表达式模式的多个方法的调用。特别地,查询表达式被转换成名为 Where, Select, SelectMany, OrderBy, OrderByDescending, ThenBy, ThenByDescending 和 GroupBy 的方法调用,这些方法预期拥有特别的签名 和返回类型,描述于§。这些方法可以是被查询对象的实例方法或者对象外部的扩展方法,它们实现了 实际上的查询的执行过程。
从查询表达式到方法调用的转换(translation),是发生在任何类型绑定或重载决议执行之前的语法映射。 转换要求保证语法上的正确,但不保证产生语义正确的 C#代码。查询表达式的转换之后,产生的方法 调用作为常规函数调用被处理,并且这可能依次暴露出错误。比如如果方法不存在,再比如参数类型 错误或者方法是泛型的而类型推导失败。
查询表达式的转换通过一系列例子示范如下。正式的转换规则的描述在后面部分。
26.7.1.1 where 子句 查询表达式中的 where 子句:
from c in customers
where c.City == "
select c
转换成带有通过结合迭代变量标识符和 where 子句表达式合成的 lambda 表达式的 Where 方法。
customers.
Where(c => c.City == "
26.7.1.2 select 子句
前面部分的例子示范了选择最内层迭代变量的 select 子句是如何通过转换成方法调用而被移除的。
Select 子句选择最内层迭代变量以外的东西:
from c in customers
where c.City == "
select c.Name
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 17
Overview of C# 3.0
转换成合成的 lambda 表达式的 Select 方法的调用:
customers.
Where(c => c.City == "
Select(c => c.Name)
26.7.1.3 group 子句
group 子句:
from c in customers
group c.Name by c.Country
转换成 GroupBy 方法的调用
customers.
GroupBy(c => c.Country, c => c.Name)
26.7.1.4 orderby 子句
orderby 子句:
from c in customers orderby c.Name
select new { c.Name, c.Phone }
转换成 OrderBy 方法的调用,或者如果递减方向被指定时,转换成 OrderByDescending 方法的调用。
customers.
OrderBy(c => c.Name).
Select(c => new { c.Name, c.Phone })
orderby 子句中的第二个(secondary)次序
from c in customers
orderby c.Country, c.Balance descending
select new { c.Name, c.Country, c.Balance }
转换成对 ThenBy 和 ThenByDescending 方法的调用:
customers.
OrderBy(c => c.Country).
ThenByDescending(c => c.Balance).
Select(c => new { c.Name, c.Country, c.Balance })
26.7.1.5 多重产生器 多重产生器:
from c in customers
where c.City == "
from o in c.Orders
where o.OrderDate.Year == 2005
select new { c.Name, o.OrderID, o.Total }
除了最内层产生器之外,所有的都转换成对 SelectMany 的调用,:
customers.
Where(c => c.City == "
SelectMany(c =>
c.Orders.
Where(o => o.OrderDate.Year == 2005).
Select(o => new { c.Name, o.OrderID, o.Total })
)
当多重产生器与 orderby 子句结合时:
18 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
from c in customers, o in c.Orders where o.OrderDate.Year == 2005 orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
额外的 Select 被注入进来搜集排序表达式(ordering expressions)和元组序列中的最终结果。 有必要这样
做,以至 OrderBy 可以操作于整个序列上。OrderBy 之后,最终结果从元组中提取出来。
customers. SelectMany(c => c.Orders.
Where(o => o.OrderDate.Year == 2005).
Select(o => new { k1 = o.Total, v = new { c.Name, o.OrderID, o.Total } })
).
OrderByDescending(x => x.k1).
Select(x => x.v)
26.7.1.6 info 子句
info 子句:
from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Group.Count() }
它是内嵌查询的一个简单而更方便的表示法:
from g in
from c in customers group c by c.Country
select new { Country = g.Key, CustCount = g.Group.Count() }
转换如下:
customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Group.Count() })
26.7.2 查询表达式模式
查询表达式模式建立了一个方法的模式,类型可以实现它来支持查询表达式。因为查询表达式利用语 法映射转变成方法调用,类型在如何实现查询表达式模式时有很大的灵活性。例如:模式的方法可以 被实现成实例方法或扩展方法,因为这两者具有同样的调用语法;并且方法可以要求(request)
delegates 或表达式树,因为 lambda 可以转换成这两者。
推荐使用的支持查询表达式模式的泛型类型 C<T>的外形(shape)显示如下。泛型类型被使用来例示参数 和返回类型的适当关系,但是非泛型类型实现这个模式同样是可能的。
delegate R Func<A,R>(A arg);
class C<T>
{
public C<T> Where(Func<T,bool> predicate);
public C<S> Select<S>(Func<T,S> selector);
public C<S> SelectMany<S>(Func<T,C<S>> selector);
public O<T> OrderBy<K>(Func<T,K> keyExpr);
public O<T> OrderByDescending<K>(Func<T,K> keyExpr);
public C<G<K,T>> GroupBy<K>(Func<T,K> keyExpr);
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 19
Overview of C# 3.0
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keyExpr, Func<T,E> elemExpr);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T>
{
public K Key { get; }
public C<T> Group { get; }
}
上面的方法使用泛型 delegate 类型 Func<A, R>,但是同样可以以参数和返回类型中的相同关系来使用其
它 delegate 或表达式树类型。
注意,推荐的 C<T>和 O<T>之间的关系保证仅在 OrderBy 或 OrderByDescending 的返回结果上 ThenBy
和 ThenByDescending 方法是可用的。也请注意,推荐的 GroupBy 结果的外形是分组的序列,每个分组 具有 Key 和 Group
属性(property)。
标准的查询操作符 (描述于单独的规范中)提供任意实现了 System.Collections.Generic.IEnumerable<T>
接口的查询操作符模式的实现。
26.7.3 正式的转换规则
查询表达式通过依次重复应用下述转换进行处理。每个转换被应用,直到不出现指定的模式为止。
注意,在产生 OrderBy 和 ThenBy 调用的转换中,如果对应的排序子句指定了一个递减方向的指示器,
OrderByDescending 或 ThenByDescending 就会产生。
· 包含 into 子句的查询
q1 into x q2
转换成
from x in ( q1 ) q2
· 带有多个产生器的 from 子句
from g1 , g2 , … gn
转换成
from g1 from g2 … from gn
· 立即跟随 where 子句的 form 子句
from x in e where f
被转换成
from x in ( e ) . Where ( x => f )
· 多个 from,一个 orderby 和一个 select 子句的查询表达式
from x
被转换成
20 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
( from x
select new { k1 = k1 , k2 = k2 … , v = v } )
. OrderBy ( x => x . k1 ) . ThenBy ( x => x . k2 ) …
. Select ( x => x . v )
· 多个 from,一个 orderby 和一个 group 子句的查询表达式
from x
被转换成
( from x
select new { k1 = k1 , k2 = k2 … , v = v , g = g } )
. OrderBy ( x => x . k1 ) . ThenBy ( x => x . k2 ) …
. GroupBy ( x => x . g , x => x . v )
· 多个 from 和一个 select 子句的查询表达式
from x in e from x
被转换成
( e ) . SelectMany ( x => from x
· 多个 from 子句和一个 group 子句的查询表达式
from x in e from x
被转换成
( e ) . SelectMany ( x => from x
· 一个 from,没有 orderby,一个 select 子句的查询表达式
from x in e select v
被转换成
( e ) . Select ( x => v )
除非当 v 是标识符 x 时,转换都是简单的
( e )
· 一个 from,没有 orderby,一个 group 子句的查询表达式
from x in e group v by g
被翻译成
( e ) . GroupBy ( x => g , x => v )
除了当 v 是标识符 x,转换是
( e ) . GroupBy ( x => g )
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 21
Overview of C# 3.0
· 一个 from,一个 orderby,一个 select 子句的查询表达式
from x in e orderby k1 , k2 … select v
被转换成
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) … . Select ( x => v )
除非当 v 是标识符 x 时,翻译是简单的。
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) …
· 一个 from,一个 orderby,一个 group 子句的查询表达式
from x in e orderby k1 , k2 … group v by g
被转换成
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) …
. GroupBy ( x => g , x => v )
除非 v 是标识符 x 时,转换是
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) … . GroupBy ( x => g )
26.8 表达式树(Expression trees)
表达式树允许 lambda 表达式表示为数据结构而不是可执行代码。可转型到 delegate 类型 D 的 lambda 表 达式也可以转型到类型 System.Query.Expression<D>。然而,到 delegate 类型的 lambda 表达式的转型 导
致 建立表达式树实例的代码产生(emit)。表达式树是 lambda 表达式的高效内存数据表示,并生成表达 式转换的结构,且使其显式化。
前面的例子表示一个 lambda 表达式即作为可执行代码,又作为表达式树。因为存在到 Func<int,int>的 转型,也存在到 Expression<Func<int,int>>的转型。
Func<int,int> f = x => x + 1; // Code
Expression<Func<int,int>> e = x => x + 1; // Data
紧随这些参数之后,delegate f 引用返回 x+1 的方法,表达式树 e 引用描述表达式 x+1 的数据结构。
注意:
表达式树的结构将在单独的规范中。这个规范在 PDC 2005 技术预览会议上还不存在。
22 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述