C# Language Specification 30CnWord文件下载.docx
- 文档编号:906555
- 上传时间:2023-04-29
- 格式:DOCX
- 页数:36
- 大小:38.99KB
C# Language Specification 30CnWord文件下载.docx
《C# Language Specification 30CnWord文件下载.docx》由会员分享,可在线阅读,更多相关《C# Language Specification 30CnWord文件下载.docx(36页珍藏版)》请在冰点文库上搜索。
26.7.1.4orderbyclausesorderby语句29
26.7.1.5Multiplegenerators多个发生器30
26.7.1.6intoclausesinto语句31
26.7.2Thequeryexpressionpattern查询表达式模式31
26.7.3Formaltranslationrules正式转换规则32
26.8Expressiontrees表达式树35
26.C#3.0概述
C#3.0(“C#Orcas”)引入了一些语言的扩展,这些扩展是基于C#2.0做出的,并且支持到创新和更高端的命令,功能性风格的类库。
这些扩展使得对于结构性的API的解释变得有效,并且使得C#能够像查询语言在某些领域(例如关系型数据库和XML)一样,具有同等的威力。
这些扩展包括:
∙隐式类型本地变量,能够允许这种类型的本地变量被用来初始化它们的表达式所推断出来的类型。
∙扩展函数,使得扩展现有的类型和构造类型使用额外的函数成为可能。
∙Lambda表达式,匿名函数的一种进化,为代理类型和表达式树提供改良的类型推断结果和转换。
∙对象构造者,使得构造和初始化对象更容易。
∙匿名类型,它从tuple类型自动被推断并且由对象构造者生成的。
∙隐式类型化的数组,一种数组建立的规范并且初始化以从数组构造者那里推断出数组中的元素类型。
∙查询表达式,提供了一种语言内置的查询句法,有点类似关系型和等级型查询语言,例如SQL和Xquery。
∙表达式树,允许lambda表达式被表示为数据(表达式树)以替代被表示为代码(代理)。
本文是对于上述特征的一些技术概观。
文档引用了C#LanguageSpecification1.2($1到$18)以及theC#LanguageSpecification2.0(§
19到§
25),这些都可以在C#LanguageHomePage(
26.1Implicitlytypedlocalvariables隐式类型化的本地变量
在一个隐式类型化的本地变量和声明中,本地变量类型的声明过程是由使用的表达式初始化变量来推断的。
当一个本地变量声明标示为var作为类型并且没有var类型名称在范围内,那么这个声明被视作隐式类型化的本地变量声明。
例如:
vari=5;
vars="
Hello"
;
vard=1.0;
varnumbers=newint[]{1,2,3};
varorders=newDictionary<
int,Order>
();
如上所述的隐式类型化的本地变量声明相当于下列显式的声明:
inti=5;
strings="
doubled=1.0;
int[]numbers=newint[]{1,2,3};
Dictionary<
orders=newDictionary<
一个本地变量声明在一个隐式类型化的本地变量声明中具有以下约束:
∙声明者必须包含一个构造者。
∙这个构造者必须是一个表达式。
这个构造者不能够是一个对象或者构造者集合($26.4)的自身,但是它可以是一个新的包含一个对象或者构造者集合的表达式。
∙在编译时刻构造者表达式的类型不能为null类型。
∙如果本地变量声明包含多种声明者,那么构造者必须都具有相同的编译时刻类型。
以下是一些隐式类型化本地变量声明的错误例子:
varx;
//Error,noinitializertoinfertypefrom
vary={1,2,3};
//Error,collectioninitializernotpermitted
varz=null;
//Error,nulltypenotpermitted
由于向后兼容性的原因,当一个本地变量声明标示为var作为类型并且在范围内已经有了一个var的类型,那么声明指向的是该类型;
然而,编译器会产生该种混淆的警告。
由于将类型声明为var违反了已经发布的关于将类型名称首字母大写的约定,所以这种情况不太可能发生。
for语句(§
8.8.3)的for-initializer以及using语句的resource-acquisition能够作为隐式类型化本地变量声明。
同样的foreach语句的迭代变量也可以用来声明隐式类型化本地变量,这是为了迭代变量类型被表示为枚举的集合元素类型。
在这个例子中
int[]numbers={1,3,5,7,9};
foreach(varninnumbers)Console.WriteLine(n);
类型n被表示为int,这是numbers的元素类型
26.2Extensionmethods扩展函数
扩展函数是static函数,它能够使用实例函数句法调用。
扩展函数有效地使得扩展现有类型以及使用额外函数构造类型变为可能
扩展函数在功能性函数中比在实例函数中更少被使用并且更有局限性。
由于这些理由,我们推荐吝啬地使用扩展函数并且仅仅当实例函数行不通或不可能使用的时候才使用它。
其余类型的扩展成员,例如属性,事件,以及操作符,正在考虑中,但是现在还不被(编译器)支持。
26.2.1声明扩展函数
扩展函数使用特定的关键字this作为修饰符应用于函数的第一个参数来声明。
扩展函数只能声明在static的类中,以下是一个static类,声明两个扩展函数的例子:
namespaceAcme.Utilities
{
publicstaticclassExtensions
{
publicstaticintToInt32(thisstrings){
returnInt32.Parse(s);
}
publicstaticT[]Slice<
T>
(thisT[]source,intindex,intcount){
if(index<
0||count<
0||source.Length–index<
count)
thrownewArgumentException();
T[]result=newT[count];
Array.Copy(source,index,result,0,count);
returnresult;
}
}
扩展函数具有常规static函数的一切能力。
额外的,一旦导入,扩展函数能够被使用实例函数的句法所调用。
26.2.2导入扩展函数
扩展函数通过using-namespace-directives(§
9.3.2)导入。
另外导入的有,该namespace所包含的类型,using-namespace-directive导入所有的扩展函数在该namespace中的所有static类。
被导入的扩展函数有效地作为在某些类型上的额外函数,这是通过它们的第一个关键字以及比常规的实例函数更低的优先级所给与的。
例如,当Acme.Utilitiesnamespace在下面这个例子的开头被使用using-namespace-directive导入后,
usingAcme.Utilities;
这使得通过实例函数句法调用static类Extensions的扩展函数成为可能:
1234"
inti=s.ToInt32();
//SameasExtensions.ToInt32(s)
int[]digits={0,1,2,3,4,5,6,7,8,9};
int[]a=digits.Slice(4,3);
//SameasExtensions.Slice(digits,4,3)
26.2.3Extensionmethodinvocations扩展函数的调用
扩展函数调用的详细规则将在下面描述。
在函数调用(§
7.5.5.1)任一形式中
expr.identifier()
expr.identifier(args)
expr.identifier<
typeargs>
()
(args)
如果正常的调用过程中没有发现适用的实例函数(尤其如果对此调用的候选函数为空的情况下),编译器将做出尝试将构造过程作为扩展函数来调用。
函数调用是先各自重写下列中的一个的:
identifier(expr)
identifier(expr,args)
identifier<
(expr)
(expr,args)
重写的格式是被视作static函数调用处理,除非identifier被解析:
开始使用最近的namespace声明,接着使用次近namespace声明,最后包含编译单元,逐次尝试被用来处理重写函数调用,这是通过一个函数群实现的,该函数群由所有准入的扩展函数所组成,它的名称是由identifier给与的,identifier导入是由名称空间声明的using-namespace-directives实现的。
第一个函数群产生一套非空的候选函数,其中有一个函数会被选中,来作为重写函数的调用。
如果所有的尝试都产生空的候选函数,这将导致一个编译时刻的错误。
前述的规则意味着实例函数优先于扩展函数,并且名称空间内的扩展函数优先于通过名称空间导入的扩展函数。
usingN1;
namespaceN1
publicstaticclassE
publicstaticvoidF(thisobjectobj,inti){}
publicstaticvoidF(thisobjectobj,strings){}
classA{}
classB
publicvoidF(inti){}
classC
publicvoidF(objectobj){}
classX
staticvoidTest(Aa,Bb,Cc){
a.F
(1);
//E.F(object,int)
a.F("
hello"
);
//E.F(object,string)
b.F
(1);
//B.F(int)
b.F("
c.F
(1);
//C.F(object)
c.F("
//C.F(object)
在例子中,B的函数优先于前面的扩展函数,并且C的函数优先于所有的扩展函数。
26.3表达式
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-parameter-list:
explicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter-list,explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter:
parameter-modifieropttypeidentifier
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=>
一些lambda表达式的例子如下所示:
x=>
x+1//隐式类型,表达式主体
{returnx+1;
}//隐式类型,声明主体
(intx)=>
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)。
PDC2005技术预览编译器不支持lambda表达式含有声明块主体。
一旦要是使用声明块主体,C#2.0的匿名函数标记必须被使用。
26.3.1表达式的转换
和匿名函数表达式类似,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的某个函数:
delegateRFunc<
(Aarg);
下列付值中:
Func<
int,int>
f1=x=>
x+1;
//Ok
int,double>
f2=x=>
//Ok
double,int>
f3=x=>
//Error
每个Lambda表达式的参数和返回类型决定于分配给lambda表达式的变量的类型。
第一个委派成功地转换lambda表达式为代理类型Func<
,因为,当x给与类型int,x+1是一个确实的表达式,可以隐式地转化类型到int。
同样的第二个委派成功地转化lambda表达式为代理类型Func<
,因为x+1的结果(int类型)可以隐式地转化为类型double。
然后,第三个委派会产生一个编译时刻错误,因为,当x给与double类型,x+1的结果(类型为double)不能够隐式地转化为类型int.
26.3.2类型推断
当不使用特定类型的参数调用一个泛型函数时,类型推断会尝试为此次调用推断参数类型。
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类中:
namespaceSystem.Query
publicstaticclassSequence
publicstaticIEnumerable<
S>
Select<
T,S>
(
thisIEnumerable<
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# Language Specification 30Cn 30 Cn