首页 CSharp语言高级特性

CSharp语言高级特性

举报
开通vip

CSharp语言高级特性第章C#语言高级特性学习目标>理解Attribute特性类>理解C#2.0中的新特性>理解C#3.0中的新特性Attribute特性概述Attribute特性是一种描述性的信息,是应用程序的"元数据"的一部分,即描述数据的数据。一般用于修饰应用程序中程序集、程序集中的类型、类型的成员、方法的参数等。特性与受它修饰的语言元素绑定在一起,编译器和运行时环境能读取特性信息,这样将影响编译行为或运行时行为。.net框架中有一些预定义的Attribute特性。如影响编译器行为的Obsolete特性。请看示例。usingSys...

CSharp语言高级特性
第章C#语言高级特性学习目标>理解Attribute特性类>理解C#2.0中的新特性>理解C#3.0中的新特性Attribute特性概述Attribute特性是一种描述性的信息,是应用程序的"元数据"的一部分,即描述数据的数据。一般用于修饰应用程序中程序集、程序集中的类型、类型的成员、方法的参数等。特性与受它修饰的语言元素绑定在一起,编译器和运行时环境能读取特性信息,这样将影响编译行为或运行时行为。.net框架中有一些预定义的Attribute特性。如影响编译器行为的Obsolete特性。请看示例。usingSystem;publicclassAnyClass([Obsolete("Don'tuseOldmethod,useNewmethod",true)]staticvoidOld()()staticvoidNew()()publicstaticvoidMain()(Old();}}在上该实例中我们用到了名为Obsolete的attribute特性,它标记了一个不该再被使用的语言元素(这里的元素为方法),该特性的第一个参数是string类型,它解释为什么该元素被废弃,以及我们该使用什么元素来代替它。实际中,我们可以 关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf 写任何其它文本来代替这段文本。第二个参数是告诉编译器应该把依然使用这被标识的元素视为一种错误,这就意味着编译器会因此而产生一个警告。当我们试图编译上面的上面的程序,我们会得到如下错误:AnyClass.Old()'isobsolete:'Don'tuseOldmethod,useNewmethod'1.1.2自定义特性类除了直接使用这些框架中预定义的特性之外,我们也可以自己定义特性。在C#中,我们自定义的attribute特性都要派生自System.Attribute类。除此之外,就像定义一个普通的类一样。请看示例。usingSystem;publicclassHelpAttribute:Attribute(}现在就可以用它来修饰我们的类了。[Help()]publicclassAnyClass(}注意:按惯例我们是用"Attribute”作为attribute类名的后缀,然而,当我们当我们把attribute绑定到某语言元素时,是不包含“Attribute"后缀的。编译器首先在System.Attribute的继承类中查找该attribute,如果没有找到,编译器会把“Attribute"追加到该attribute的名字后面,然后查找它。继续扩展名为Help的特性类。usingSystem;publicclassHelpAttribute:Attribute(publicHelpAttribute(StringDescrition_in)(this.description=Description_in;}protectedStringdescription;publicStringDescription(get(returnthis.description;}}}[Help("thisisado-nothingclass")]publicclassAnyClass(}在上面的例子中,我们在attribute类中添加了一个只读属性。在运行时可以使用反射读取该属性的值。控制自定义Attribute的用法可以使用名为AttributeUsage的特性类来控制自定义Attribute的用法。AttributeUsage类是预定义的特性类(Attribute类本身就用这个atrributeSystem.AttributeUsage来标记),它描述了一个自定义attribute类能被怎样使用。也就是说,它将帮助我们控制我们自定义attribute的用法。AttributeUsage提供三个属性:ValidOn通过这个属性,我们能指定我们的自定义attribute可以放置在哪些语言元素之上。此属性的类型为名为AttributeTargets的枚举,默认值为AttributeTargets.All。AllowMultiple该属性标识我们的自定义attribte是否能在同一语言元素上使用多次。该属性为bool类型,默认值为false,意思就是该自定义attribute在同一语言元素上只能使用一次。Inherited该属性来控制我们的自定义attribute类的继承规则。即我们的自定义attribute是否可以由派生类继承。该属性为bool类型,默认值为false,意思是不能被继承。首先来看ValidOn属性。修改上例。usingSystem;[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]publicclassHelpAttribute:Attribute(publicHelpAttribute(StringDescription_in)(this.description=Description_in;}protectedStringdescription;publicStringDescription(get(returnthis.description;}}}我们注意到它规定这个helpattribute只能放置在语言元素"class"之上。现在试着把它绑定到方法。[Help("thisisado-nothingclass")]publicclassAnyClass([Help("thisisado-nothingmethod")]//errorpublicvoidAnyMethod()(}}编译时将会产生一个错误。AnyClass.cs:Attribute'Help'isnotvalidonthisdeclarationtype.Itisvalidon'class'declarationsonly.我们可以使用AttributeTargets.All来允许Helpattribute可以放置在任何一种语言元素上,那些可能的语言元素如下:Assembly,Module,Class,Struct,Enum,Constructor,Method,Property,Field,Event,Interface,Parameter,Delegate,All=Assembly|Module|Class|Struct|Enum|Constructor|Method|Property|Field|Event|Interface|Parameter|Delegate,ClassMembers=Class|Struct|Enum|Constructor|Method|Property|Field|Event|Delegate|Interface接下来理解AllowMultiple属性。修改上例。[Help("thisisado-nothingclass")][Help("itcontainsado-nothingmethod")]//errorpublicclassAnyClass(publicvoidAnyMethod()(}}这将产生一个编译错误:AnyClass.cs:Duplicate'Help'attribute这是说:该attribute不能在同一语言元素上放置多次最后理解Inherited属性。假设有如下代码。[Help("BaseClass")]publicclassBase(}publicclassDerive:Base(}Inherited属性指出,当把该attribute放置于一个基类之上,是否派生类也继承了该attribute。如果绑定至某个attribute类的Inherited被设为true,那么该attribute就会被继承,反之如果绑定至某个attribute类的Inherited被设为false或者没有定义,那么该attribute就不会被继承。可选参数vs.命名参数可选参数是attribute类构造函数的参数。它们是强制的,在attribute绑定每次至某语言元素时必须提供一个值。而另一方面,命名参数是真正的可选参数,不是在attribute构造函数的参数。为了更加详细的解释,让我们在Help类中添加另外的属性。[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]publicclassHelpAttribute:Attribute(publicHelpAttribute(StringDescription_in)(this.description=Description_in;this.verion="NoVersionisdefinedforthisclass";}protectedStringdescription;publicStringDescription(get(returnthis.description;}}protectedStringversion;publicStringVersion(get(returnthis.version;}set(this.verion=value;}}}[Help("ThisisClass1")]publicclassClass1(}[Help("ThisisClass2”,Version="1.0")]publicclassClass2(}[Help("ThisisClass3",Version="2.0",Description="Thisisdo-nothingclass")]publicclassClass3(}我们不应为了可选参数而使用多个构造函数,应该用已命名参数来代替。我们之所以称它们为已命名的,是因为当我们在构造函数为它们提供值时,我们必须命名它们。例如,在第二个类中,我们如是定义Help。[Help("ThisisClass2",Version="1.0")]在AttributeUsage例子中,参数"ValidOn"是可选参数,而"Inherited"和"AllowMultiple“是命名参数。注意:为了在attribute的构造函数中设定命名参数的值,我们必须为相应的属性提供一个set方法否则会引起编译期错误:'Version':Namedattributeargumentcan'tbeareadonlyproperty这背后的原因为:首先带有可选参数的构造函数被调用,然后,每个命名参数的set方法被调用,在构造函数中赋给命名参数的值被set方法所覆写。•特性类的参数类型一个attribute类的参数类型被限定在如下类型中:bool,byte,char,double,float,int,long,short,stringSystem.Typeobjectenum•特性标识符假设,我们想把Helpattribute绑定至assembly元素。明显的问题是我们要把Helpattribute放在哪儿才能让编译器确定该attribute是绑定至整个assembly呢?另一个问题,我们想把attribute绑定至一个方法的返回类型上,怎样才能让编译器确定我们是把attribute绑定至方法的返回类型上,而不是整个方法呢?为了解决诸如此类的含糊问题,我们使用attribute标识符,有了它的帮助,我们就可以确切地申明我们把attribute绑定至哪一个语言元素。例如:[assembly:Help("thisado-nothingassembly")]这个在Helpattribute前的assembly标识符确切地告诉编译器,该attribute被绑定至整个assembly。可能的标识符有:assemblymoduletypemethodpropertyeventfieldparamreturn1.2泛型1.2.1什么是泛型我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?在C#2中出现的泛型的就是专门解决这个问题的。1.2.2为什么要使用泛型为了了解这个问题,我们先看下面的代码,代码省略了一些 内容 财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容 ,但功能是实现一个栈,这个栈只能处理int数据类型:publicclassStack(privateint[]m_item;publicintPop()(...}publicvoidPush(intitem)(...}publicStack(inti)(this.m_item=newint[i];}}上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:publicclassStack(privateobject[]m_item;publicobjectPop()(...}publicvoidPush(objectitem)(...}publicStack(inti)(this.m_item=new[i];}}这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):Node1x=newNode1();stack.Push(x);Node2y=(Node2)stack.Pop();上面的代码在编译时是完全没问题的,但由于Push了一个Nodel类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。针对object类型栈的问题,我们引入泛型,可以优雅地解决这些问题。泛型用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。1.2.3使用泛型下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:publicclassStack(privateT[]m_item;publicTPop()(...)publicvoidPush(Titem)(...}publicStack(inti)(this.m_item=newT[i];}}类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法://实例化只能保存int类型的类Stacka=newStack(100);a.Push(10);Push("8888");/这一行编译不通过,因为类a只接收int类型的数据intx=a.Pop();//实例化只能保存string类型的类Stackb=newStack(100);b.Push(10);//这一行编译不通过,因为类b只接收string类型的数据Push("8888”);stringy=b.Pop();这个类和object实现的类有截然不同的区别:他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。无需类型转换。1.2.4泛型类的实例化C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:泛型类的不同的封闭类是分别不同的数据类型。例:Stack和Stack是两个完全没有任何关系的类,你可以把他看成类A和类B,这个解释对泛型类的静态成员的理解有很大帮助。1.2.5泛型类中数据类型的约束程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定丁的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。这时就用到了C#2.0的新增关键字where:publicclassNodewhereT:Stack,IComparablewhereV:Stack{...}以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。通用类型T没有特指,但因为C#中所有的类都是从object继承来,所以他在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如你的类 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 只需要支持两种数据类型int和string,并且在类中需要对T类型的变量比较大小,但这些却无法实现,因为object是没有比较大小的方法的。了解决这个问题,只需对T进行IComparable约束,这时在类Node里就可以对T的实例执彳亍CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。如果在类Node里需要对T重新进行实例化该怎么办呢?因为类Node中不知道类T到底有哪些构造函数。为了解决这个问题,需要用到new约束:publicclassNodewhereT:Stack,new()whereV:IComparable需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int,long,struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:publicclassNodewhereT:classwhereV:struct1.2.6泛型方法泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:publicclassStack2{publicvoidPush(Stacks,paramsT[]p){foreach(Ttinp){s.Push(t);}}}原来的类Stack一次只能Push一个数据,这个类Stack2扩展了Stack的功能(当然也可以直接写在Stack中),他可以一次把多个数据压入Stack中。其中Push是一个泛型方法,这个方法的调用示例如下:Stackx=newStack(100);Stack2x2=newStack2();x2.Push(x,1,2,3,4,6);strings="";for(inti=0;i<5;i++)(s+=x.Pop().ToString();}/至此,s的值为643211.2.7泛型中的静态成员变量在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:Stacka=newStack();Stackb=newStack();Stackc=newStack();类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。1.2.8泛型中的静态构造函数静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。泛型中的静态构造函数的原理和非泛型类是一样的只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:1.2.特定的封闭类第一次被实例化。特定封闭类中任一静态成员被调用。1.2.9泛型类中的方法&载方法的重载在.NetFramework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:publicclassNode(publicTadd(Ta,Vb)第一个add(returna;}publicTadd(Va,Tb)第二个add(returnb;}publicintadd(inta,intb)涕三个add(returna+b;}}上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:Nodenode=newNode();objectx=node.add(2,11);这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。Nodenode=newNode();objectx=node.add(2,"11");这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。1.2.10泛型类的方法重写方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。1.2.11泛型的使用范本节主要讲解在类中使用泛型,实际上,泛型还可以用在类方法、接口、结构(structX委托等上面使用,使用方法大致相同,在这里不再详细介绍。1.3局部类型1.3.1什么是局部类型?C#2.0引入了局部类型的概念。局部类型允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。局部类型适用于以下情况:•类型特别大,不宜放在一个文件中实现。•一个类型中的一部分代码为自动化工具生成的代码不宜与我们自己编写的代码混合在一起。•需要多人合作编写一个类。局部类型是一个纯语言层的编译处理,不影响任何执行机制——事实上C#编译器在编译的时候仍会将各个部分的局部类型合并成一个完整的类。publicpartialclassProgram(staticvoidMain(string[]args)(}partialclassProgram(publicvoidTest()(}}1.3.2局部类型的限制使用局部类型时注意以下限制:局部类型只适用于类、接口、结构,不支持委托和枚举。同一个类型的各个部分必须都有修饰符partial。使用局部类型时,一个类型的各个部分必须位于相同的命名空间中。一个类型的各个部分必须被同时编译。1.3.3局部类型的注意点关键字partial是一个上下文关键字,只有和class、struct、interface放在一起时才有关键字的含义。因此partial的引入不会影响现有代码中名称为partial的变量。局部类型的各个部分一般是分开放在几个不同的.cs文件中,但C#编译器允许我们将他们放在同一文件中。1.3.4在局部类型上应用Attribute特性在局部类型上标注的特性具有"累加"效应。[Attribute】,Attribute2("Hello")]partialclassClass1(}[Attributes,Attribute2("Exit")]partialclassClass1(}相当于[Attributel,Attribute2("Hello"),Attributes,Attribute2("Exit")]classClassl(}注:假设Attribute2属性允许在类上多次使用。1.3.5局部类型上的修饰符一个局部类型的各个部分上的访问修饰符必须维持一致性。•如果一个局部类型有一个部分使用了abstract修饰符,那么整个类都将被视为抽象类。•如果一个局部类型有一个部分使用了sealed修饰符,那么整个类都将被视为密封类。一个局部类的各个部分不能使用相互矛盾的修饰符,比如不能在一个部分上使用abstract,又在另一个部分上使用sealed。1.3.6局部类型的基类和接口一个局部类型的各个部分上指定的基类必须一致。某个部分可以不指定基类,但如果指定,则必须相同。局部类型上的接口具有"累加"效应。partialclassClass2:linterfacel,Iinterface2()partialclassClass2:Iinterface3()partialclassClass2:Iinterface2()相当于classClass2:linterfacel,Iinterface2,Iinterface3()1.4局部方法局部方法是C#3.0引入的一个新的语法特性。局部方法使得方法声明和方法实现可以分布到两个不同的cs文件中。请看示例:publicpartialclassMyClass(//声明方法partialvoidSomeMethod();}publicpartialclassMyClass(//实现方法partialvoidSomeMethod()(Console.WriteLine("Hello,worod");}}但是要注意的是,该方法只能用于类型内部访问。试想一下,我们在设计一个类型的时候,可能会有这样的情况:某个操作可能是需要的,但在类型设计的前期还不知道如何实现。所以,我们需要先放置一个占位符。请看示例:publicpartialclassMyClass(//声明方法partialvoidSomeMethod();publicvoidTest()(SomeMethod();〃这个方法要调用SomeMethod,但SomeMethod目前还不知道如何实现MyClass类型内部有一个Test方法,它试图要去调用SomeMethod,即便现在还不知道如何实现后者。如果后来一直到代码编译时都还没有实现SomeMethod方法也没有关系,编译器会检测这种情况。如果某个方法虽然声明了,但没有实现,它就不会被编译,所有对其的调用代码也会被移除。很显然,因为这种特性,局部方法只适用于void类型的方法,即没有返回值的。事实上,局部方法是完全依赖于局部类来实现的,没有局部类就不可能有局部方法。局部方法的主要使用场景是一些代码生成工具用于处理轻量级事件以及通过属性赋值时实现数据验证或者通过一个属性的set访问器去更新另一个属性。注意使用局部方法时的限制:声明局部方法时,必须使用到关键字Partial来声明;•局部方法只能声明在局部类中;局部方法一定是私有成员,在类外部无法对调用局部方法;•局部方法不能够有返回值,即返回类型只能为void。1.5扩展方法在C#3之前,如果我们想为一个已编译的类型扩展自定义逻辑的方法时,我们必须自定义一个新的类型来继承已有类型的方式来添加方法。使用这种继承方式来添加方法时,我们必须自定义一个新的派生类型,如果基类有抽象方法还需要重新去实现抽象方法。这样为了扩展一个方法却会导致因继承而带来的其他的开销(自定义一个派生类,还要覆盖基类的抽象方法等)所以使用继承来为现有类型扩展方法时就有点大才小用的感觉了。并且当我们需要为值类型和密封类(不能被继承的类)或静态类这些不能被继承的类型扩展新的方法时,此时继承就不能被我们所用了。所以在C#3中提出了用扩展方法来实现为现有类型添加方法。扩展方法使您能够向现有类型"添加"方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。使用扩展方法来实现扩展可以解决使用继承中所带来的弊端。以下定义和调用扩展方法的一般步骤:1、定义一个静态类以包含扩展方法。该类必须对客户端代码可见。2、将该扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。3、该方法的第一个参数指定方法所操作的类型;该参数必须以this修饰符开头。4、在调用代码中,添加一条using指令以指定包含扩展方法类的命名空间。5、按照与调用类型上的实例方法一样的方式调用扩展方法。下面的示例演示了向字符串类提供名为WordCount的扩展方法用于统计字符串对象中的单词个数。请看示例。usingSystem.Linq;usingSystem.Text;usingSystem;namespaceCustomExtensions(//扩展方法必须定义在静态类中publicstaticclassStringExtension(//这是一个扩展方法//第一个参数必须使用this修饰//第一个参数的类型表明此方法是为这个类型扩展的publicstaticintWordCount(thisStringstr)(returnstr.Split(newchar[]('','.','?'},StringSplitOptions.RemoveEmptyEntries).Length;}}}namespaceExtension_Methods_Simple(/启入定义扩展方法的静态类所属的命名空间usingCustomExtensions;classProgram(staticvoidMain(string[]args)(strings="Thequickbrownfoxjumpedoverthelazydog.";/调用扩展方法inti=s.WordCount();System.Console.WriteLine("Wordcountofsis{0}",i);}}}下面的示例演示了向名为Grades的枚举类型提供名为Passing的扩展方法用于判断某个成绩是否合格。usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.Linq;namespaceEnumExtension{publicstaticclassExtensions{publicstaticGradesminPassing=Grades.D;/成绩的及格线publicstaticboolPassing(thisGradesgrade){returngrade>=minPassing;}}publicenumGrades(F=0,D=1,C=2,B=3,A=4};classProgram(staticvoidMain(string[]args)(Gradesg1=Grades.D;Gradesg2=Grades.F;Console.WriteLine("First{0}apassinggrade.",g1.Passing()?"is":"isnot");Console.WriteLine("Second{0}apassinggrade.",g2.Passing()?"is":"isnot");Extensions.minPassing=Grades.C;/更改了成绩的及格线Console.WriteLine("\r\(更改了成绩的及格线!\r\n");Console.WriteLine("First{0}apassinggrade.",g1.Passing()?"is":"isnot");Console.WriteLine("Second{0}apassinggrade.",g2.Passing()?"is":"isnot");}}}}1.6自动属性在C#3之前我们定义属性时,一般会首先会先定义私有字段,再定义属性来对字段进行访问。请看示例。publicclassPerson{privatestring_name;publicstringName{get{return_name;}set{_name=value;}}}在C#3中,当我们在类中定义的属性不需要额外的验证逻辑或计算逻辑时,此时我们可以使用自动实现的属性使属性的定义更加简洁。请看示例。publicclassPerson{publicstringName{get;set;}publicintAge(get;privateset;}publicPerson(stringname)(Name=name;}}不定义私有字段并不是此时没有了私有字段而是编译器帮我们生成一个匿名的私有字段,不需要我们编写声明字段并用属性访问字段的代码,减少我们书写的代码量。1.7对象和集合初始化器在C#3.0之前,我们可能会使用各种重载的构造函数来初始化对象。请看示例。namespace对象集合初始化器Demo(classProgram(staticvoidMain(string[]args)(Personperson1=newPerson();person1.Name="learninghard";person1.Age=20;Personperson2=newPerson("learninghard");person2.Age=25;}}}有了对象初始化特性之后我们就不需要考虑定义参数不同的构造函数来应付不同情况的初始化了,这样就减少了在我们类中定义的构造函数代码,这样使代码更加简洁。请看示例。namespace对象集合初始化器Demo(classProgram(staticvoidMain(string[]args)(Personperson3=newPerson(Name="learninghard",Age=25};Personperson4=newPerson()(Name="learninghard",Age=25};〃上面两行代码是等价的//如果类没有无参的构造函数就会出现编译时错误//也可以先调用有参构造函数,再使用对象初始化器Personperson5=newPerson("learninghard")(Age=25};}}}在C#3之前要对集合初始化,需要先实例化集合本身,然后将集合元素逐个添加到集合对象中。请看示例。namespace对象集合初始化器Demo(classProgram(staticvoidMain(string[]args)(Listnames=newList();names.Add("learninghard1");names.Add("learninghard2”);names.Add("learninghard3");}}}使用C#3的集合初始化器特性可对集合初始化代码进行简化。请看示例。namespace对象集合初始化器Demo(classProgram(staticvoidMain(string[]args)(Listnames=newList("learninghard1”,"learninghard2","learninghard3"};}}}集合初始化同样是编译器自动帮我们调用List的无参构造函数,然后调用Add()方法逐个地将集合元素添加进去。对于编译器而言,C#3中使用集合初始化的代码和C#3之前写的代码是一样.然而对于开发人员来说,有了C#3的集合初始化之后,这个过程就不需要我们自己去编码,而是交给编译器帮我们做了。1.8隐式类型局部变量在C#3之前要定义一个局部变量,必须明确声明它的类型。请看示例。staticvoidMain(string[]args)(stringstr="learninghard";intnum=100;}C#3的编译器能够根据变量的值来推断变量的类型,这样,变量的类型就不需要明确声明,而是使用关键字var来代替任何类型。请看示例。staticvoidMain(string[]args)(varstr="learninghard";varnum=100;}用关键字var定义的变量则该变量就是为隐式类型,var关键字告诉编译器根据变量的值类推断变量的类型。所以对于编译器而言,隐式类型同样也是显式的,同样具有一个明确的类型。注意:被声明的变量是一个局部变量,不能为字段(包括静态字段和实例字段)。变量在声明时必须被初始化(因为C#是静态语言,必须在定义变量时指定变量的类型。在使用隐式类型的情况下,编译器要根据变量的赋值来推断变量的类型,如果没有被初始化则编译器就无法推断出变量类型,就会出现编译时错误)。变量不能初始化为null(因为null可以隐式转化为任何引用类型或可空类型,所以编译器不能推断出该变量到底应该为什么类型)。不能用var来声明方法中的参数类型。var不仅可以创建隐式类型的局部变量,还可以创建隐式类型的数组。请看示例。//编译器推断为int[]类型varintarray=new[](1,2,3,4};//编译器推断为string[]类型varstringarray=new[]("hello","learninghard"};//无法编译varerrorarray=new[]("hello",3};使用隐式类型的数组时,编译器必须推断出使用什么类型的数组,编译器首先会构造一个包含大括号里面的所有表达式(如上面代码中的1,2,3,4和"hello”,"learninghard")的编译时类型的集合,在这个集合中如果所有类型都能隐式转换为唯一的一种类型,则该类型就成为数组的类型,否则,就会出现编译时错误。如代码中隐式类型数组出错的情况,因为"hello”转化为string,而3却转化为int,此时编译器就不能确定数组的类型到底为什么,所以就会出现编译错误,错误信息为:”找不到隐式类型数组的最佳类型”。1.9匿名类型在C#3之前要使用某种类型,必须先明确地定义这种类型。既先有类型后有对象。C#3编译器能根据我们的意图自动生成匿名类型,这样当要临时使用一个简单的类型时,就不必显式地定义这个类型了。请看示例。namespace匿名类型Demo(classProgram(staticvoidMain(string[]args)(〃赋值运算符右边的代码将由编译器创建为匿名类型,并实例化这个类型的对象。这个类型包含Name和Age这两个属性〃匿名类型没有类型名称,所以声明这种类型的变量时,只能用隐式类型。varperson1=new(Name="learninghard”,Age=25};Console.WriteLine("{0}年龄为:{1}”,personl.Name,personl.Age);}}}还可以创建匿名类型的数组。请看示例。namespace匿名类型Demo{classProgram{staticvoidMain(string[]args){varpersoncollection=new[]{new{Name="Tom”,Age=30},new{Name="Lily",Age=22},new{Name="Jerry",Age=32}//,//如果加入下面一句就会出现编译时错误//因为此时编译器就不能推断出统一的匿名类型//new{Name="learninghard"}};inttotalAge=0;foreach(varpersoninpersoncollection)//下面代码 证明 住所证明下载场所使用证明下载诊断证明下载住所证明下载爱问住所证明下载爱问 Age属性是强类型的int类型totalAge+=person.Age;}Console.WriteLine(”所有人的年龄总和为:{0}”,totalAge);}}}1.1(匿名方法匿名方法是C#2.0引入的一个新特性,它允许开发者内联声明自己的方法代码而无须显式地实例化委托对象并包裹命名方法。在C#2.0之前,声明和使用委托要求你有委托类型和一个在委托被调用时具有匹配签名的能够执行的命名方法,以及一个将命名方法与委托关联的分配语句——这是C#2.0之前版本中,实例化委托的唯一方法。作为C#2.0的新特性,匿名方法基本上能够提供与先前命名方法相同的功能,但是它已经不再需要一个在关联到委托之前就明确创建的方法了。你可以把C#匿名方法想象为一个实现与委托进行关联这项功能的便捷途径。当编译器碰到匿名方法的时候,它会在类里面创建一个命名方法,并将它与委托进行关联。所以匿名方法在运行期间与命名方法的性能非常类似——性能的增加体现在开发人员的生产效率上,而不是运行期间的执行上。下面的示例演示实例化委托的两种方式:使委托与匿名方法关联。使委托与命名方法关联。两种方法都会在调用委托时显示一条消息。请看示例:classProgram{//声明一个委托。//Declareadelegate.delegatevoidPrinter(strings);staticvoidDoWork(stringk){Console.WriteLine(k);}staticvoidMain(string[]args){//这段代码演示使用命名方法来实例化委托Printerp=newPrinter(Program.DoWork);p("Thedelegateusingthenamedmethodiscalled.");//这段代码演示使用匿名方法来实例化委托p=delegate(stringj)(System.Console.WriteLine(j);};p("Thedelegateusingtheanonymousmethodiscalled.");}}匿名方法通常用于:•需要一个临时方法,该方法仅使用一次;•方法体的代码很短,甚至可能比方法声明还短。1.11Lambda表达式“Lambda表达式"是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。所有Lambda表达式都使用Lambda运算符=>,该运算符读为“goesto"。该Lambda运算符的左边是输入参数(如果有),右边包含表达式或语句块。Lambda表达式x=>x*x读作“xgoestoxtimesx”。可以将此表达式分配给委托类型,如下所示:delegateintdel(inti);staticvoidMain(string[]args)(delmyDelegate=x=>x*x;intj=myDelegate(5);//j=25}在前面的示例中,请注意委托签名具有一个int类型的输入参数,并返回int。可以将Lambda表达式转换为该类型的委托,因为该表达式也具有一个输入参数(x),以及一个编译器可隐式转换为int类型的返回值。(以下几节中将对类型推理进行详细讨论。)使用输入参数5调用委托时,它将返回结果25。=>运算符具有与赋值运算符(=)相同的优先级,并且是右结合运算符。Lambda在基于方法的LINQ查询中用作 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 查询运算符方法(如Where)的参数。在is或as运算符的左侧不允许使用Lambda。适用于匿名方法的所有限制也适用于Lambda表达式。表达式在右边的Lambda表达式称为“Lambda表达式”。Lambda表达式在构造表达式树(C#和VisualBasic)时广泛使用。Lambda表达式返回表达式的结果,并采用以下基本形式:(inputparameters)=>expression只有在Lambda有一个输入参数时,括号才是可选的;否则括号是必需的。两个或更多输入参数由括在括号中的逗号分隔:(x,y)=>x==y有时,编译器难于或无法推断输入类型。如果出现这种情况,您可以按以下示例中所示方式显式指定类型:(intx,strings)=>s.Length>x使用空括号指定零个输入参数:()=>SomeMethod()Lambda语句Lambda语句与Lambda表达式类似,只是语句括在大括号中:(inputparameters)=>(statement;}Lambda语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。delegatevoidTestDelegate(strings);♦♦♦TestDelegatemyDel=n=>(strings=n+""+"World";Console.WriteLine(s);};myDel("Hello");带有标准查询运算符的Lambda许多标准查询运算符都具有输入参数,其类型是泛型委托的Func(OfT,TResult)系列的其中之一。Func(OfT,TResult)委托使用类型参数定义输入参数的数目和类型,以及委托的返回类型。Func委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。例如,假设有以下委托类型:publicdelegateTResultFunc(TArg0arg0)可以将委托实例化为FuncmyFunc,其中int是输入参数,bool是返回值。始终在最后一个类型参数中指定返回值。Func定义包含两个输入参数(int和string)且返回类型为bool的委托。在调用下面的Func委托时,该委托将返回true或false以指示输入参数是否等于5:FuncmyFunc=x=>x==5;boolresult=myFunc(4);//returnsfalseofcourse当参数类型为Expression时,您也可以提供Lambda表达式,例如在System.Linq.Queryable内定义的标准查询运算符中。如果指定Expression参数,Lambda将编译为表达式树。此处显示了一个标准查询运算符,Count方法:int[]numbers=(5,4,1,3,9,8,6,7,2,0};intoddNumbers=numbers.Count(n=>n%2==1);编译器可以推断输入参数的类型,或者您也可以显式指定该类型。这个特别的Lambda表达式将计算整数(n)的数量,这些整数除以2时余数为1。以下方法将生成一个序列,其中包含数字数组中在"9"左边的所有元素,因为"9”是序列中不满足条件的第一个数字:varfirstNumbersLessThan9=numbers.TakeWhile(n=>n<9);此示例演示如何通过将输入参数括在括号中来指定多个输入参数。该方法将返回数字数组中的所有元素,直至遇到一个值小于其位置的数字为止。不要将Lambda运算符(=>)与大于等于运算符(>=)混淆。Lambda中的类型推理在编写Lambda时,通常不必为输入参数指定类型,因为编译器可以根据Lambda主体、基础委托类型以及C#语言规范中描述的其他因素推断类型。对于大多数标准查询运算符,第一个输入是源序列中的元素的类型。因此,如果要查询IEnumerable,则输入变量将被推断为Customer对象,这意味着您可以访问其方法和属性:customers.Where(c=>c.City=="London");Lambda的一般规则如下:Lambda包含的参
本文档为【CSharp语言高级特性】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_704284
暂无简介~
格式:doc
大小:46KB
软件:Word
页数:29
分类:
上传时间:2018-05-18
浏览量:1