C# Language Specification 3.0 Cn

 
 
 
 
 

C #
版本3.0
2005 年9月

注意
© 2006 Microsoft Corporation。保留所有权利.
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.

Table of Contents
26. Overview of C# 3.0.................................................. 5
26.1 Implicitly typed local variables 隐式类型化的本地变量 ....................... 6
26.2 Extension methods 扩展函数 ................................................. 7
26.2.1 Declaring extension methods 声明扩展函数 ................................ 8
26.2.2 Importing extension methods 导入扩展函数 ................................ 8
26.2.3 Extension method invocations 扩展函数的调用 ............................. 9
26.3 Lambda expressions Lambda 表达式 .......................................... 10
26.3.1 Lambda expression conversions Lambda 表达式的转换 ...................... 12
26.3.2 Type inference 类型推断 ............................................... 14
26.3.3 Overload resolution 重载决断 .......................................... 17
26.4 Object and collection initializers 对象以及集合构造者 ..................... 18
26.4.1 Object initializers 对象构造者 ........................................ 19
26.4.2 Collection initializers 集合构造者 .................................... 21
26.5 Anonymous types 匿名类型 .................................................. 23
26.6 Implicitly typed arrays 隐式类型化数组 .................................... 25
26.7 Query expressions 查询表达式 .............................................. 26
26.7.1 Query expression translation 查询表达式翻译 ........................... 28
26.7.1.1 where clauses where 语句 .......................................... 28
26.7.1.2 select clauses select 语句 ......................................... 29
26.7.1.3 group clauses group 语句 ........................................... 29
26.7.1.4 orderby clauses orderby 语句 ....................................... 29
26.7.1.5 Multiple generators 多个发生器 ..................................... 30
26.7.1.6 into clauses into 语句 ............................................. 31
26.7.2 The query expression pattern 查询表达式模式 ........................... 31
26.7.3 Formal translation rules 正式转换规则 ................................. 32
26.8 Expression trees 表达式树 ................................................. 35

26. C# 3.0概述
C# 3.0(“C# Orcas”) 引入了一些语言的扩展,这些扩展是基于C#2.0做出的,并且支持到创新和更高端的命令,功能性风格的类库。这些扩展使得对于结构性的API的解释变得有效,并且使得C#能够像查询语言在某些领域(例如 关系型数据库和XML)一样,具有同等的威力。这些扩展包括:
·         隐式类型本地变量,能够允许这种类型的本地变量被用来初始化它们的表达式所推断出来的类型。
·         扩展函数,使得扩展现有的类型和构造类型使用额外的函数成为可能。
·         Lambda 表达式,匿名函数的一种进化,为代理类型和表达式树提供改良的类型推断结果和转换。
·         对象构造者,使得构造和初始化对象更容易。
·         匿名类型,它从tuple 类型自动被推断并且由对象构造者生成的。
·         隐式类型化的数组,一种数组建立的规范并且初始化以从数组构造者那里推断出数组中的元素类型。
·         查询表达式,提供了一种语言内置的查询句法,有点类似关系型和等级型查询语言,例如SQL和Xquery。
·         表达式树,允许lambda表达式被表示为数据(表达式树)以替代被表示为代码(代理)。
本文是对于上述特征的一些技术概观。文档引用了C# Language Specification 1.2($1到$18)以及the C# Language Specification 2.0 (§19 到§25),这些都可以在C# Language Home Page ( http://msdn.microsoft.com/vcsharp/language ) 上得到。
26.1 Implicitly typed local variables隐式类型化的本地变量
在一个隐式类型化的本地变量和声明中,本地变量类型的声明过程是由使用的表达式初始化变量来推断的。当一个本地变量声明标示为var作为类型并且没有var类型名称在范围内,那么这个声明被视作隐式类型化的本地变量声明。例如:
var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
如上所述的隐式类型化的本地变量声明相当于下列显式的声明:
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>();
一个本地变量声明在一个隐式类型化的本地变量声明中具有以下约束:
·         声明者必须包含一个构造者。
·         这个构造者必须是一个表达式。这个构造者不能够是一个对象或者构造者集合($26.4)的自身,但是它可以是一个新的包含一个对象或者构造者集合的表达式。
·         在编译时刻构造者表达式的类型不能为null类型。
·         如果本地变量声明包含多种声明者,那么构造者必须都具有相同的编译时刻类型。
以下是一些隐式类型化本地变量声明的错误例子:
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违反了已经发布的关于将类型名称首字母大写的约定,所以这种情况不太可能发生。
for 语句 (§8.8.3) for-initializer 以及using语句的 resource-acquisition 能够作为 隐式类型化本地变量声明。同样的foreach语句的迭代变量也可以用来声明隐式类型化本地变量,这是为了迭代变量类型被表示为枚举的集合元素类型。在这个例子中
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
类型n被表示为int,这是numbers的元素类型
扩展函数是static函数,它能够使用实例函数句法调用。扩展函数有效地使得扩展现有类型以及使用额外函数构造类型变为可能
注意
扩展函数在功能性函数中比在实例函数中更少被使用并且更有局限性。由于这些理由,我们推荐吝啬地使用扩展函数并且仅仅当实例函数行不通或不可能使用的时候才使用它。
其余类型的扩展成员,例如属性,事件,以及操作符,正在考虑中,但是现在还不被(编译器)支持。
扩展函数使用特定的关键字this作为修饰符应用于函数的第一个参数来声明。扩展函数只能声明在static的类中,以下是一个static类,声明两个扩展函数的例子:
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;
     }
}
}
扩展函数具有常规static函数的一切能力。额外的,一旦导入,扩展函数能够被使用实例函数的句法所调用。
扩展函数通过 using-namespace-directive s (§9.3.2) 导入。另外导入的有,该namespace所包含的类型, using-namespace-directive 导入所有的扩展函数在该namespace中的所有static类。被导入的扩展函数有效地作为在某些类型上的额外函数,这是通过它们的第一个关键字以及比常规的实例函数更低的优先级所给与的。例如,当 Acme.Utilities namespace 在下面这个例子的开头被使用 using-namespace-directive 导入后,
using Acme.Utilities;
这使得通过实例函数句法调用static类Extensions 的扩展函数成为可能:
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)
扩展函数调用的详细规则将在下面描述。在函数调用(§7.5.5.1)任一形式中
expr . identifier ( )
expr . identifier ( args )
expr . identifier < typeargs > ( )
expr . identifier < typeargs > ( args )
如果正常的调用过程中没有发现适用的实例函数(尤其如果对此调用的候选函数为空的情况下),编译器将做出尝试将构造过程作为扩展函数来调用。函数调用是先各自重写下列中的一个的:
identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )
重写的格式是被视作static函数调用处理,除非 identifier 被解析:开始使用最近的namespace声明,接着使用次近namespace声明,最后包含编译单元,逐次尝试被用来处理重写函数调用,这是通过一个函数群实现的,该函数群由所有准入的扩展函数所组成,它的名称是由identifier给与的,identifier导入是由名称空间声明的using-namespace-directives实现的。第一个函数群产生一套非空的候选函数,其中有一个函数会被选中,来作为重写函数的调用。如果所有的尝试都产生空的候选函数,这将导致一个编译时刻的错误。
前述的规则意味着实例函数优先于扩展函数,并且名称空间内的扩展函数优先于通过名称空间导入的扩展函数。例如:
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 { }
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的函数优先于所有的扩展函数。
C#2.0 引入了匿名函数,它允许代码块能够被写成“内联”在代理值所期望的地方。当匿名函数提供功能性编程语言的巨大威力的同时,匿名函数的标记也显得相当的冗长。 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
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表达式被转化为一个匹配的代理类型,也就是那个代理类型提供参数的类型。
在一个具有唯一的,显式类型参数的lambda表达式中,圆括号可以从参数列表中删除。换句话说,一个这种类型的lambda表达式
( param ) => expr
可以被缩写为
param => expr
一些lambda表达式的例子如下所示:
x => x + 1                           // 隐式类型, 表达式主体
x => { return x + 1; }           //隐式类型, 声明主体
(int x) => x + 1                 // 显式类型, 表达式主体
(int x) => { return x + 1; } //显式类型, 声明主体
(x, y) => x * y                  // 多个参数
() => Console.WriteLine()        // 无参
通常,匿名函数规范,C#2.0规范中§21提供的,也适用于lambda表达式。Lambda表达式是匿名函数功能性的超集,提供了下列额外的功能:
·         Lambda 表达式允许参数类型被删除和推断,而匿名函数需要参数类型的显式声明。
·         Lambda 表达式的主体可以是一个表达式或者是一个声明块,而匿名函数的主体只能是声明块。
·         Lambda 表达式传递为参数参与类型参数的推论(§ 26.3.2 ) 并且在函数中重载论断(§ 26.3.3 )
·         Lambda 表达式具有一个表达式主体能够被转化为表达式树(§ 26.8 )
注意
PDC 2005技术预览编译器不支持lambda表达式含有声明块主体。一旦要是使用声明块主体,C# 2.0的匿名函数标记必须被使用。
和匿名函数表达式类似,lambda表达式可以被类化为一个具有特殊转化规则的值。这个值没有类型,但是可以被显式地转化为匹配的代理类型。如,一个代理类型D是匹配一个lambda表达式L提供了:
·         D 和L具有相同数量的参数。
·         如果L具有一个显式类型参数列表,每个在D中的参数具有相同类型和修饰符作为和L相匹配参数。
·         如果L具有一个隐式类型的参数列表,D不能够有ref或者out的参数。
·         如果D是void返回类型并且L的主体是一个表达式,当每个L的参数被给与在D中的关联参数类型时,L的主体是一个确实的表达式,可以被允许作为声明表达式(§8.6).
·         如果D为void返回类型并且L的主体是一个声明块,当每个L的参数被给与在D中的关联参数类型时,L的主体是一个确实的声明块,该声明块没有返回声明特指的表达式。
·         如果D不是void返回类型并且L的主体是一个表达式,当每个L的参数被给与在D中的关联参数类型时,L的主体是一个确实的表达式,可以隐式地转化为D的返回类型。
·         如果D不是void返回类型并且L的主体是一个声明块,当每个L的参数被给与在D中的关联参数类型时,L的主体是一个确实的,具有一个不可到达的终止点的声明块,该声明块每个返回声明特指一个表达式可以隐式地转化为D的返回类型。
下列例子是用泛型代理类型 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表达式为代理类型 Func<int,int> ,因为,当x给与类型int,x + 1是一个确实的表达式,可以隐式地转化类型到int。同样的第二个委派成功地转化lambda表达式为代理类型 Func<int,double> ,因为x + 1的结果(int类型)可以隐式地转化为类型double。然后,第三个委派会产生一个编译时刻错误,因为,当x给与double类型,x + 1的结果(类型为double)不能够隐式地转化为类型int.
当不使用特定类型的参数调用一个泛型函数时,类型推断会尝试为此次调用推断参数类型。Lambda表达式传递参数到泛型函数,且参与这次类型推断的过程。
如§20.6.4所描述的那样,类型推断首先发生在每个独立的参数中。在初始化的阶段,如果参数是lambda表达式,那么什么也不推断。然而,紧跟着初始化阶段,额外的推断将被做出,这是使用一个迭代的过程所完成的。尤其是,只要下列条件为真,那么一或多个参数的推断就会被做出:
 
·         参数是一个lambda表达式,下称L,并且还没有被推断过类型。
·         相应的参数类型,下称P,是一个代理类型并且具有一个返回值,而该返回值关联一或多个函数类型参数。
·         P 和 L 具有相同数量的参数,并且每个在P中的参数具有相同的修饰符,就如同相应的L的参数,或者可以没有修饰符,如果L具有一个隐式类型的参数列表。
·         P 的参数类型没有参与函数类型参数或者仅参与的函数类型参数是为了一套一致的,已经被做出的推断。
·         如果L具有显式类型参数列表,且当推断的类型作为P的函数类型参数的替代品时,每个P中的参数具有相同的,和相应的L中的参数一样的类型。
·         如果L具有一个隐式的参数列表,且当推断的类型作为P的函数类型参数的替代品并且结果参数类型已经由L的参数所给于时,L的主体是一个确实的表达式或者声明块。
·         能够为L的返回类型进行推断,如上所述。
对于每个这样的参数,推断是从参数做出的,这个过程是通过相关联的P的返回类型以及推断的L的返回类型实现的,并且,新的推断会加入到一套已经积累完毕的推断中。这个过程重复到没有推断可以被做出。
类型推断和重载决断的目的是为了,某个lambda表达式L的推断类型如下而决定的:
 
·         如果L的主体是一个表达式,那么表达式的类型是从L的返回类型所推断的。
·         如果L的主体是一个声明块,且如果这套由表达式类型格式化的在该声明块中的返回声明包含确实的一种类型,并且这套类型中每个类型都是可以被隐式转换的,再且那类型不为null类型,那么类型可以从L的返回类型所推断的。
·         相反的,则返回类型不能够由L推断做出。
作为一个包含lambda表达式的类型推断的例子,考虑Select这个扩展函数,它被声明在 System.Query.Sequence类中:
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<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);
Select 的扩展函数调用(§ 26.2.3 ) 是通过重写为static函数掉用来处理的:
IEnumerable<string> names = Sequence.Select(customers, c => c.Name);
既然类型参数没有被显式指定,那么,只有使用类型推断来推导该类型参数。首先,customers的参数关联到源参数,推断T为Customer。接着,使用lambda表达式类型推断进行处理,如上所述,c 被给于Customer类型,并且表达式c.Name关联于一个select语句的返回类型参数,推断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);
过程如下:首先,参数 "1:15:30" 关联于值参,推断X为string。那么,第一个lambda表达式的参数s推断类型为string,并且TimeSpan.Parse(s)表达式关联于f1的返回类型,推断Y为System.TimeSpan类型。最后,第二个lambda表达式的参数t,给于得推断类型为System.TimeSpan,并且t.TotalSeconds表达式关联于f2的返回类型,推断Z为double类型。那么,该调用的结果类型为 double。
在特定情况中,lambda表达式在某个参数列表中会影响重载决断。
下列规定的参数§7.4.2.3:给与一个lambda表达式L,存在某个推断的返回类型(§ 26.3.2 ) ,L的某个对于代理类型 D1 隐式转换比对于另一个代理类型 D2 的隐式转换来得好些,如果 D1 D2 具有同一的参数列表,并从对于 D1 的返回类型而言的L的推断类型而来的隐式转换比对于 D2 的返回类型而言的L的推断类型而来的隐式转换要好些。如果相反的情况,则亦反之。
下列范例说明了这条规则。
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参数,这从列表项里分离出总和的值。分离的值即可以是int或者double并且总和的结果不是int就是double。
范例中的Sum函数能够被用来计算某个定购中详细行的总和。
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);
...
}
在首先对于derDetails.Sum的调用中,所有的Sum函数都是适用的,因为lambda表达式d => d.UnitCount对于Func<Detail,int>和Func<Detail, double>都是兼容的。然而,重载决断会选择第一个Sum函数因为,Func<Detail, int>的转换要比Func<Detail, double>的转换来得好些。
在第二个orderDetails.Sum的调用中,仅第二个Sum函数适用,因为,lambda表达式 d => d.UnitPrice * d.UnitCount 产生了一个类型为double的值。那么,重载决断为此调用选择第二个Sum函数。
某个对象建立表达式(§7.5.10.1)可能包括一个对象或集合构造者,正是它们初始化新建立的对象成员或新建立的集合成员。
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
某个对象建立表达式能够删除构造函数的参数列表和圆括号,以提供包含的对象或集合的构造者。删除构造函数参数列表和圆括号相当于明确一个空的参数列表。
对象建立表达式的执行包括某个对象或集合构造者,并由第一个调用实例构造函数组成,并且,接着执行成员或元素初始化,这是由对象或集合构造者特定化的。
对于某个对象或集合的构造者而言,引用该对象实例进行初始化是不可能的。
对象构造者特指对于某个对象的某一或几个field或属性值而言。
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
对象构造者由一系列成员构造者组成,是由{和}标示的并由逗号分隔的。每个成员构造者必须命名一个准入的对象的field或属性,当该对象被初始化的时候,紧跟着通过等号付值以及某个表达式或某个对象或集合的构造者。对于某个对象构造者,想要包含多于一个的对象构造者,却对于同一个field或属性而言,那是错误的。
成员构造者特指某个等号后面的表达式,等同于对于field或属性的付值分配。
成员构造者特指某个等号后面的对象构造者,是内嵌对象的初始化。替代对于field或属性分配新值,对象构造者的付值视同对于field或属性的对象付值。值类型的属性不能够通过这种构造进行初始化。
成员构造者特指等号后面的集合构造者是,内嵌集合的初始化。替代对于field或属性分配新值,构造者所给予的元素,通过field或属性添加至集合的引用。Field或属性必须为集合类型,且满足于在§ 26.4.2 中特指的需求。
以下类代表一个点
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 类的实例能够如下初始化建立:
var a = new Point { X = 0, Y = 1 };
这和下面的影响是等同的
var a = new Point();
a.X = 0;
a.Y = 1;
下面类代表从两个点建立的矩形:
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;
集合构造者特指集合中的元素。
collection-initializer:
{    element-initializer-listopt   }
{    element-initializer-list   ,    }
element-initializer-list:
element-initializer
element-initializer-list  
,    element-initializer
element-initializer:
non-assignment-expression
集合构造者包含一组元素构造者,这些元素构造者由{}括起并且由逗号分隔。每个元素构造者特指某个能够在集合对象被初始化的过程中能够加入其中的元素。为了避免与成员构造者之间的混淆,元素构造者不能够被分配表达式。 non-assignment-expression 主题定义在 §26.3.
下面的例子是关于某个对象建立表达式,包含一个集合构造者:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
对于应用集合构造者的集合对象必须是某个实现了System.Collections.Generic.ICollection<T>中某个确实的T的类型。进一步说,隐式的转换必须存在于,对于T每个元素的类型。如果这些要求得不到满足,则会引发编译时刻的错误。集合构造者依顺序为每个特定的元素调用ICollection<T>.Add(T)函数。
下面的类表示了某个具有名字和一系列电话号码的联系人:
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" }
}
};
这和下面的代码同样效果
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是临时变量,并且应该是不可见和不得准入。
C#3.0 允许新的操作符被用来作为匿名对象构造者以建立匿名类型的对象。
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
匿名对象构造者声明匿名对象并且返回该类型的某个实例。匿名类型是没有名称的类类型,它直接继承于object。匿名类型的成员是一组能够读写的属性,它们推断于对象构造者(们)用来建立该类型的实例。尤其是,此形式匿名对象构造者。
new { p1 = e1 , p2 = e2 , pn = en }
声明此形式的匿名类型
class __Anonymous1
{
private
T1 f1 ;
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类型,则会引发编译时刻的错误。
匿名类型的名称是由编译器自动生成的,并且不能够在程序中被引用。
在同一个程序中,两个匿名对象构造者特指一组相同名称并且类型处于相同顺序属性,将会产生相同匿名类型的实例。(这个定义包括属性的顺序,因为在特定情形下是值得注意并且是重要的,例如反射。)
例子
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
最后一行的付值是允许的,因为p1和p2是相同的匿名类型。
成员声明者能够缩写为简单的名称(§7.5.2) 或者 成员准入(§7.5.4). 这被称作投射初始者或者同一名称属性声明和付值的速记。尤其是,如下形式的成员声明
identifier                          expr . identifier
是明确等同于下列,分别为:
identifer = identifier                 identifier = expr . identifier
那么,在某个投射初始者中, identifier 为值已经被分配的既挑选值也挑选field或属性。直观地说,投射构造不仅仅是个值,并且还有该值的名称。
数组建立的表达式标记(§7.5.10.2)被扩展为支持隐式类型数组的建立表达式
array-creation-expression:

new    [    ]    array-initializer
在隐式类型数组建立的表达式中,数组实例的类型是从数组构造者特指的元素推断而来的。尤其是,在数组构造者中,这套表达式类型必须包含确实的某种类型以使每个在此套类型中的元素能够隐式地向其类型转化,并且,如果该类型不是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[]。另一种选择是,其中的某个元素能够被转化成某个通用的基类型,使得最终的推断类型就是该元素类型。
隐式类型化的数组建立表达式能够和匿名对象构造者联合使用,以建立匿名类型化的数据结构。例如:
var contacts = new[] {
new {
     Name = "Chris Smith",
     PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
     Name = "Bob Harris",
     PhoneNumbers = new[] { "650-555-0199" }
}
};
查询表达式 提供了,语言集成化的标记,为了和那些关系型或者等级型查询语言,例如SQL和Xquery相类似的查询。
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
select-or-group-clause:
select-clause
group-clause
select-clause:
select    expression
group-clause:
group    expression   by    expression
into-clause:
into    identifier   query-body
query-expression 分类为 non-assignment-expression , 定义在 §26.3.
查询表达式以from语句开始并且以select或者group语句结束。初始from语句可以跟随0个或多个from或where语句。每个from语句都是一个产生范围覆盖某个序列的迭代的发生器,且每个where语句都是一个过滤器,可以从结果中排除一些不要的内容。最后的select或group语句特指根据迭代变量包含结果。Select或group语句也许先于指定顺序结果的order语句。最终,into语句能够被用来“分割”查询,这是通过在子查询中将某个查询作为发生器来对待来进行的。
在查询表达式中,具有多个发生器的from语句严格等同于多个连续的仅有单独的发生器from语句。
在C#3.0语言不特指查询表达式的确实执行语意。进一步说,C#3.0转换查询表达式为函数调用,这是坚持query expression pattern. 尤其是,查询表达式被转换为名称为Where, Select, SelectMany, OrderBy, OrderByDescending, ThenBy, ThenByDescending, 以及 GroupBy函数调用,这些希望具有特定标记和返回类型,如在$26.7.2中表述的。这些函数能够是查询对象或对象外部的扩展函数的实例函数,并且他们实现了实际上的查询执行。
查询表达式转换为函数调用方法是依据造句法按图索骥,而这发生在任何类型绑定或重载决断被执行之前。转换被保证造句上的正确,但不保证对于C#代码的语法正确性。下列查询表达式的翻译,函数调用的结果被处理为正规的函数调用,并且可能不覆盖错误,例如,如果函数并不存在,如果参数为错误的类型,或如果函数是泛型且推断类型结果失败。
以下一系列例子展示了查询表达式的转换。关于转换规则的正式表述将在下一节表述。
查询表达式中的where语句:
from c in customers
where c.City == "London"
select c
转换使用合成的lambda表达式建立Where函数调用,通过组合迭代变量表示符以及where语句表达式来实现:
customers.
Where(c => c.City == "London")
前一节的例子展示了怎样通过转换函数调用删除一个选择内置最多迭代变量的select语句。
Select 语句选择一些多余内置最多的迭代变量:
from c in customers
where c.City == "London"
select c.Name
转换到Select函数调用,通过合成的lambda表达式:
customers.
Where(c => c.City == "London").
Select(c => c.Name)
group 语句:
from c in customers
group c.Name by c.Country
转换到GroupBy函数调用:
customers.
GroupBy(c => c.Country, c => c.Name)
orderby 语句
from c in customers
orderby c.Name
select new { c.Name, c.Phone }
转换到OrderBy函数调用,或OrderByDescending函数如果 descending 指向被指定:
customers.
OrderBy(c => c.Name).
Select(c => new { c.Name, c.Phone })
在orderby语句中的第二种排序:
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 })
多个发生器:
from c in customers
where c.City == "London"
from o in c.Orders
where o.OrderDate.Year == 2005
select new { c.Name, o.OrderID, o.Total }
转换到SelectMany函数为内置最多发生器的调用:
customers.
Where(c => c.City == "London").
SelectMany(c =>
c.Orders.
Where(o => o.OrderDate.Year == 2005).
Select(o => new { c.Name, o.OrderID, o.Total })
)
当多个发生器通过orderby语句组合:
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语句注入以收集排序表达式并且最终的结果是一系列的tuples。这是必要的,由于OrderBy能够操作于整个序列。下列OrderBy,最终结果是从tuples摘录出来的:
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)
Into 语句:
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() })
查询表达式模式 建立了类型能够实现以支持查询的函数模式表达式。因为查询表达式能够被转为函数调用,通过语句上的映射类型能够更有弹性,在它们实现查询表达式模式时候。例如,函数模式能够被作为实例函数或者扩展函数来实现,因为这两者具有相同的调用标记,并且函数能够请求代理或表达式树,因为lambda表达式能够向两者转换。
推荐的能够支持查询表达式模式的泛型类型<T>,如下所示。使用一个泛型类型因为能够在参数和结果类型中插入适合的关系,但是实现非泛型模式也是可能的。
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);
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; }
}
上述函数使用泛代理类型Func<A, R>, 但是也能够很好地被用于其它代理或表达式树,通过使用相同的在参数和结果类型中的相同关系。
注意在C<T>和O<T>中的推荐关系,这保证ThenBy和ThenByDescending函数仅在OrderBy或OrderByDescending结果中有效。也请注意GroupBy的推荐结果内容,是一系列组合,其中每个都有一个Key和Group属性。
标准查询操作符(另有单独的规范描述)提供查询操作符模式为实现了System.Collections.Generic.Ienumerable<T>借口的任何类型。
查询表达式通过依次重复应用下列转换来被处理。每个转换被应用直到没有更多特定模式出现。
注意在转换中产生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语句的from语句
from x in e where f
转换为
from x in ( e ) . Where ( x => f )
·         具有多个from语句以及orderby语句和select语句的查询表达式
from x1 in e1 from x2 in e2 orderby k1 , k2 select v
转换为
( from x1 in e1 from x2 in e2
select new { k1 = k1 , k2 = k2 , v = v } )
. OrderBy ( x => x . k1 ) . ThenBy ( ­x => x . k2 )
. Select ( x => x . v )
·         具有多个from语句以及orderby语句和group语句的查询表达式
from x1 in e1 from x2 in e2 orderby k1 , k2 group v by g
转换为
( from x1 in e1 from x2 in e2
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 x1 in e1 select v
转换为
( e ) . SelectMany ( x => from x1 in e1 select v )
·         具有多个from语句以及一个group语句的查询表达式
from x in e from x1 in e1 group v by g
转换为
( e ) . SelectMany ( x => from x1 in e1 group v by g )
·         具有单个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 )
·         具有单独的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 )
表达式树允许lambda表达式能够代表数据结构替代表示为执行代码。Lambda表达式能够转换为代理类型D,也能够转换为类型为System.Query.Expression<D>的表达式树。然而,对于代理类型的lambda表达式转换会引起执行代码的产生和通过某个代理的引用,对于某个表达式类型转换引起的代码会建立表达式树的实例。表达式树会影响内存中的lambda表达式数据表示并且使表达式结构透明和显式。
下列例子代表某个lambda表达式既作为执行代码并且作为表达式树。因为存在Func<int, int>的转换,也存在对于<Func<int, int>>表达式的转换
Func<int,int> f = x => x + 1;                        // 代码
Expression<Func<int,int>> e = x => x + 1;        // 数据
跟随的这些付值,代理f引用了某个返回x + 1的方法,并且表达式树e引用某个数据结构,它描述了表达式x + 1。
 注意
表达式树的结构将会在另一个单独的规范中描述。在PDC2005技术预览中没有涉及这个规范。
 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值