c#2.0相对于c#1.1增加了很多特性,有一些特性,如新的迭代器,匿名方法等在c#2.0发布时,一般应用还看不到 有特别的意义,到了c#3.0,LINQ技术的出现,是它们变得重要起来,c#语言新特性如下:
- 自动属性和属性访问器的保护 级别
- 可空类型
- 泛型
- 代理
- 迭代器与yield关键字
- 隐式类型的局部变量
- 对象和集合初始化器
- 匿名类型
- 扩展方法
- 匿名方法和Lamda表达式
1.自动属性和属性访问器的保护级别
属性一般是来封装类型的字段,在.NET1.1的时候,首先定义私有类型的字段,然后 通过属性来访问这个字段,到了2.0这个 写法被简化了,例如:
//c#1.1中常见的属性应用
private int _id;
public int Id
{
get { return _id; }
set { _id = value; }
}
//c#2.0中访问器带有保护级别的自动属性
public int ID { get;private set;}
自动属性只要定义访问器就可以上使用,编译器在编译时刻会添加一个随机命名的字段来存储数据。在2.0版本以后,属性访问器的保护级别 也可单独定义,前提条件是 访问器的保护级别不能大于属性的保护级别。
2.可空 类型
c#语言以前只有引用类型的变量才可以赋值为null,2.0之后,出现可空类型。可空类型允许值类型变量为null,值类型变量为null的情况还是很多的,例如数据库中的字段就可以为null.
可空类型是泛型System.Nullable<T>结构体的实例,它在值类型正常范围的值的基础上,再加上一个null值。例如Nullable<Int32>,其赋值为-2147483648到2147483647之间的任意值和null值,Nullable<bool>值包括true、false或null.
可空类型的语法是Nullable<T>,也可以 写成T?,这里的T必须是值类型。T??使用两个问号,表示调用类型的默认值。
如下面的控制台应用程序所示:
3.泛型
c#语言是强类型语言,定义变量和对象都必须声明类型,这一点不同与Javascript、PHP等脚本语言。
强类型语言的优点是明显的,例如,更容易写成内存 安全的代码,执行速度更快等,但是 缺点也是存在的,比如,函数或类的通用化问题。
在泛型出现以前,如果想让一个函数的参数或类的属性实用性更广,参数或属性的数据类型可以使用Object类型,Object类型是.NET所有类型的基类,编译通过是没有问题,但使用传入的数据时,必须进行类型强制转化。
在实际开发的过程中,经常使用到集合类。在倍于泛型集合以前,ArrayList是比较常用的集合类,因为ArrayList可以装入基于 Object类型的任何元素。这样做看似非常便利,其实效率很低,代价很明显的。
ArrayList添加的元素不论是应用类型还是值类型,都会被隐式的强制转换为Object类型。并且值类型还需进行装箱操作,在读取的时候执行取消装箱 操作。装箱或是取消装箱以及类型的强制转换等操作,在.NET中是非常耗时的。
到了c#2.0版本,泛型被引入,初步解决了上述问题。它把参数类型变成了有T类代替,而T将在编译运行时刻 ,由CLR根据使用情况,用实际类型替换。泛型的使用,可以提高代码的重用率、保护类型安全和提高性能。
实现一个简单的泛型类:
泛型类在.NET框架中一般用于集合类,下面的实例用于比较ArrayList和泛型<List>的执行效率。
实例的运行结果显示,泛型List的执行效率比ArrayList快了很多。
4.代理
代理是一种类型,它定义了方法签名,可以更兼容的方法关联,然后通过代理调用这些方法。代理也可以用来把参数传递给方法,比如,事件就是代理的一种,而事件处理 程序就是通过代理的调用,把相关参数传递给方法。
代理 类似与C++函数指针,多个方法可以绑定在一个代理上面,方法不必与代理签名完全匹配。c#2.0引入了匿名方法,允许把代码块直接绑定在代理上,而c#3.0把匿名方法升级到成lambda表达式。
代理的多种样式,下面的实例演示了代理的定义、实例化、多种形式的代理绑定。
常用的代理 形式,还有一种叫做事件,使用event关键字,一般的形式如下:
public event EventHandler myEvent;
事件一般定义在类中,形式类似字段定义,类型只能用代理类型,虽然是可以用任何代理类型,但根据未然的设计规范:事件处理程序的方法采用两个参数。第一个参数是System.Object类型,名为“sender”,该参数是引发事件的对象。第二个参数是System.EventArgs类型,名为“e”,该参数是与事件关联的数据。事件处理程序方法不应返回值。一个事件处理程序可以调用多个对象中的多个方法,如果允许这些方法返回 值,则每个事件都将有多个返回值 ,并且只有调用的最后一个方法的值可用
所以 ,为了方便和符合微软的设计规范,事件的定义都采用EventHandler或泛型EventHandler<T>代理类型,这两个类型既符合设计规范,又能满足常用的事件的处理需要。
下面的Windows窗体应用程序实例演示了自定义事件及其事件数据类和利用代理跨线程的数据传递
类MyEventArgs.cs
在EventDemo.cs代码文件中,输入如下内容
实例创建了工作线程来处理循环,然后通过代理来更新界面和引发事件 ,这个操作 在WinForm程序 中经常要用到。
对于自定义事件,使用泛型的EventHandler<T>代理,类型就可以采用前面定义的MyEventArags类。
5.迭代器与yield关键字
迭代器 是.NET的一个 重要特性,它提供了一种简单 的方法来访问复杂的数据结构,在类中可以实现多个迭代器,每个迭代器和类型的成员一样有 唯一的名称。
.NET1.1时代,定义迭代器非常麻烦和复杂。到了.NET2.0以后,对迭代器的编程方式 进行了大幅度的改进,配合yield关键字,用极少 的代码,就可以完成一个迭代器,并且很多需要实现的接口方法,编译 器都会自动生成。
迭代器在LINQ中应用广泛,并且是LINQ延迟执行查询行为的基础。
yield关键字在迭代器中,用于向每句对象返回元素值或发出迭代结束信号
yield关键字的使用形式如下:
yield return <expression>;
yield break;
yield关键字只能用在定义迭代器的代码块中,yield return 返回枚举元素后,会保存迭代器的每句位置,当下次调用迭代器时,从保存的位置继续执行。
下面的控制台应用程序演示了使用yield 关键字的迭代器:
6.隐式类型的局部变量
在与方法类似的局部环境下,c#3.0定义一个变量时,可以使用var关键字来隐式 声明数据类型。
var关键字 在很多语言中都有使用,例如 JavaScript、PHP等。c#的var关键字跟其他语言不同指出就是,var声明出的变量既不是一个变体、也不是一个无类型变量,而还是一个强类型变量。
编译器是通过初始化语句右侧的表达式推断出变量类型的,所以读者也可以认为var关键字是一个“语法糖”,这个语法糖,在开发LINQ查询、枚举元素的时候,是非常高效的,因为书写LINQ表达式,有时并不需要确切知道数据类型。
有了var关键字之后,推算数据类型的任务交给编译器,开发者可以专心与LINQ表达式的设计。
var关键字除了带来编程上的便利,同时也带来一个不太好的副作用,就是会给代码的可读性增加障碍,如果大量使用 var关键字 ,代码维护 的难度将会提高。
下面的控制台应用程序DemoVar演示反编译查看用var关键字定义的变量类型
运行.NET Reflector后,打开项目输出的“DemoVar.exe”,找到Main函数,即可看反编译代码
7.对象和集合初始化器
在c#3.0中对象或集合 初始化器由一个对象创建表达式构成。对象创建表达式不使用带参数的构造器,并且在表达式中不能引用正在初始化的对象实例。
1)对象初始化器
对象初始化器由一些成员初始化器组成,包含在花括号中间,用逗号分割,每个成员初始化器以对象的一个可访问的字段或属性开始,后面跟一个等号,之后是一个表达式,或一个对象,或一个集合初始化器。如果对性初始化器内部同一个域或属性有多个成员初始化器,将会发生错误。
成员初始化器的等号后面,表达式最终结果类型要与属性或字段的类型一致,表达式中也可以嵌套其他对象初始化器。
下面的控制台应用程序DemoInitializer1演示了对象初始化器:
2)
集合初始化器
如果集合实现了泛型System.Collections.Generic.ICollections<T>接口,并且指定了T的类型,那么就可以使用集合初始化器来初始化。集合出事湖区器将对每个元素调用ICollection<T>.Add(T)方法。集合初始化器由一系列包括还在花括号之间的元素初始化器构成,元素初始化器之间使用逗号进行分割。元素初始化器不能是赋值表达式
下面的控制台应用程序DemoInitializer2演示了集合初始化器既和对象初始化器的复合使用
8.匿名类型
匿名类型一般出现在LINQ的select子句中,用来投影筛选出来的数据。匿名类型使用new运算符和对象初始值设定项来创建。
匿名类型创建的属性是只读的。匿名类型的名称和属性的数据类型有编译器随机指定或自动推断。
匿名类型不允许包含除了属性以外的成员,不能牵制转换为Object以外的类型或接口,如果把匿名类型赋值给变量,必须使用var关键字来构建此变量。
多个匿名类型具有相同的顺序、相同数量和种类的属性成员,编译器会将这些匿名类型视为相同的类型,并且它们共想编译器生成的类型信息。
下面 的控制台应用程序演示了DemoAnonynousType演示了不用写名字也可以创建一个类型:
对于定义的匿名类型,由于属性的类型、书序相同,所以这两个匿名类型生成的类型也是一致的。
9.扩展方法
给已存在的类型添加方法,除了继承类型外,c#3.o还提供叫做扩展方法的特性。扩展方法是一种建立 在非泛型、非嵌套的静态类中的特殊的静态方法。
通用扩展方法与调用类型在类型中实际定义的方法之间没有明显的差异。扩展方法是静态方法,所以无法访问类型中的内部成员。
扩展方法的第一个参数的数据类型,决定这个方法应用与哪个类型,在数据类型的前面还需标有this关键字。
LINQ中使用大量的扩展方法,绝大多数的LINQ查询标识符都是建立在集合的扩展方法上,如Select、where等
下面控制台应用程序DemoExtensionMethods演示了不用继承对象给对象添加方法。
添加新类:MyExtensionMehtod.cs
这个文件中定义静态类,3个扩展方法包含其中,String类型的扩展方法"UbbToHtmlLink()",这是一个使用正则表达式处理字符串的方法:Int32类型的扩展方法“IsEvent()”,用于检测数字是否是偶数,Program类型的扩展方法ExtensionShow(),Program类型将在“Program.cs”文件中定义。
Program.cs文件中的代码:
虽然例如JavaScript等很多语言都提供扩展方法,并且LINQ也是基于扩展建立的技术,但根据微软的设计准则,还是建议读者尽量不要使用扩展方法,如果有可能,推荐用继承的方法从类型派生出新类型。
扩展方法的签名如果和类型的成员方法重名,扩展方法永远不会被调用,扩展方法无法修改类型内部的源代码,所以在 类型升级后,扩展方法可能会失效。
10.匿名方法和Lamdba表达式
匿名方法是一种特殊的代理,他把代码体 直接关联在代理类型上,而不是常见的关联方法名。
匿名方法最早在c#2.0出现,到了c#3.0,Lamdba表达式取代了匿名方法,变成编写内联代码的首选方式。
下面的控制台应用程序DemoAnonymousMethod演示了创建匿名方法:
创建匿名方法,把代码体直接关联到DemoDelegate代理类。
Lambda表达式将“匿名方法”的语法进一步的简化。Lambda表达式还可以通过使用表达式木绿树动态编译代码,使得代码的运行效率进一步增强。
Lambda表达式的形式如下:(input parameters)=>expression
Lambd使用Lambda运算符“=>”,该运算符读为“goes to”.Lambda运算符的左边是可选项目:输入参数;右边是表达式或代码块。Lambda运算符与赋值运算符(=)是相同的优先级,并且也是右结合运算符。
Lambda语句的形式如下:
(input parameters)=>(statement)
Lambda语句与Lambda表达式基本一样,只是运算符“=>”右边是用花括号包含的代码块。
下面的控制台应用程序DemoLambda1演示了如何创建Lambda表达式
在Lambda的语句中可以 调用语句外的变量,语句内的变量则不能被外面调用,语句内的变量直到关联的代理超出范围才会被垃圾回收器 回收。
Lambda表达式目录树是Lambda表达式以树形的形式表现出来的一种数据结构。它是泛型System.Linq.Expressions.Expression<TDelegate>的实例。TDelegate代理类型即可以使用.NET Framework3.5中预定义Func系列代理类型,也可以使用自己定义的代理类型。
Lambda表达式目录树把Lambda表达式分解成树形结构,这主要是为了创建IQueryable LINQ提供程序、优化Lambd表达式、提高执行效率。
下面的控制台应用程序DemoLambda2演示了创建Lambda表达式树:
Lambda表达式目录树 负责分解、优化Lambda表达式,一般开发者只要大致了解就可以,它很少有实际应用的时候。
哈哈,最后再看个综合实例:c#高亮编辑及编译运行器
这个综合实例使用上面讲解的自动属性、扩展方法、集合初始化器、代理、隐藏类型的局部变量、Lambda
创建DemoCSharpEditor的Windows窗体应用程序
在项目中添加名称为“CurrentWord.cs”的类,代码如下:
这是一个数据存储类,有3个自动属性,用来存储检索 到的词。
在项目中添加名称为“EditorExtension.cs”的类,输入下面的代码:
这是一个静态类,定义了3个扩展方法,作用就是返回字符串中光标位置上的单词。
在项目中添加名称为“EditorController.cs”类,代码如下:
这个控制器类,初始化的时候,传入RichTextBox控件,关联上KeyUp事件,通过检测光标附近的单词是否在关键字列表中,如果是关键字,就是用RichTextBox属性,将关键字 颜色设置为LightColor属性值,如果不是关键字,设置为TextColor属性值。
界面Editor.cs的代码如下: