CLR VIA C# 学习笔记
第19章 可空类型
1)使用Nullable<T>可将int32的值类型设置为Null,CLR会在Null时默认赋值为0;
如:Nullable<T> x=null; //使用x. GetValueOrDefault()
2)也可使用?代替,如下:
Point? pt = null;//new Point();
int x1 = pt.GetValueOrDefault().X;
int y1 = pt.GetValueOrDefault().Y;
3)注意使用可空实例IL会生成大量代码
4) C#的空接合操作符
使用??操作符,假如左边的操作符不为空,则返回这个操作符的值,如果为空则使用右边的操作数的值,类型数据库中的isnull, 如:int x=b??123; //若b为空则x=123;
有人争辩说??不过是?:的语法糖,其实??能够很好的支持表达式
func<string> f=()=>somethod()??”mark”;
第12章
泛型为开发人员提供以下优势:
1) 源代码保护
2) 类型安全,试图使用不兼容的类型的一个对象会造成编译错误,或运行异常。
3) 更清晰的代码,减少代码中必须进行的转型次数
4) 更佳的性能,想要进行常规化的算法,必须定义操作object类型
12.1
1)要使用线程安全的泛型集合类,去system.collection.concurrent命名空间,建议开发人员使用泛型集合类,不鼓励使用费泛型集合. 泛型比非泛型更好的对象模型,虚方法明显减少,从而获得更好的性能,泛型集合类添加了新成员,为开发人员提供了新的功能。
12.3 泛型基本结构
泛型从CLR 2.0增加,为了加入泛型的支持,微软必须完成以下工作:
创建新的IL指令,使之能够识别类型实参
修改现有元数据表格式,以便表示具有泛型参数的名称和方法
修改JIT编译器
创建新的反射成员,使之能查询类型和成员。
修改调试以显示和操纵泛型、成员、局部变量
修改vs 的职能感应特性。
12.3.1 开发类型和封闭类型
具有泛型类型参数的类型称为开发类型,clr禁止构造开放类型的任何实例。
为所有类型实参传递的都是实际数据类型,则称为封闭类型.CLR允许构造封闭类型的实例。
Type t=typeof(Dictionary<,>); //Dictionary<,> 开放类型
t=typeof(Dictionary<Guid>) //Dicionary<Guid>是一个封闭类型
CLR会在类型对象内部分配类型的静态字段,每个封闭类型都有自己的静态字段,换言之,假如List<T>定义了任何静态字段,这些字段不会再一个List<datetime>和一个List<string>之间共享,每个粉笔类型对象都有它自己的静态字段。在泛型类型上课定义一个静态构造函数,目的是保证传递的类型实参满足特定的条件。如果希望一个泛型类型只处理枚举类型,就可以再静态构造函数进行定义判断,若类型T不是枚举,则报错。
12.3.2
例子: 1)List<Datetime> dt=new list<datetime>
一些开发人员可能首先定义下面这样的一个类:
internal sealed class DatetimeList:List<Datetime>{}
这样做表面,方便了,但是绝对不要单纯出于增强源代码的目的定义一个新类,这样会丧失类型同一性和相等性.若要这样做,可以再源码文件的顶部使用using指令
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
12.3.4 代码爆炸
假如某个类型实参是值类型,CLR必须专门为哪个类型生成本地代码。这是因为值类型的大小不定。即使两个值类型具有相同的大小(比如int32和Uint32,两者都是32位),CLR仍然无法共享代码,因为可能要用不同的本地CPU指令来操作这些值。CLR会认为所有引用类型实参都是相同的,所以代码可共享。如list<stream>与list<string>,都会使用相同的代码。
12.3.3 建议尽量使用在FCL中预定义的泛型action,func委托
12.3.4 委托的协变与逆变
1)不变量:泛型类型参数不能更改。
2)协变量: 泛型类型参数可以从一个基类更改为该类的派生类.在c#中用in关键字标记逆变量形式的泛型类型参数,逆变量泛型类型参数只出现在输入位置。比方方法的参数。
3)逆变量:泛型类型参数可以从一个派生类更改为它的基类。C#中,用out关键字标记协变量想成的泛型类型参数。协变量泛型类型参数职能楚翔在输出位置,比如方法的返回类型。
注意:只有在编译器能够验证类之间存在一个引用转换的前提下,才能应用这些可变性。
12.9.1 主要约束
类型参数可以指定一个主要约束,不能指定一下特殊引用类型:object,array,delegate,multicastDelegate,valueType,enum,void.
public class primaryclass<T> where T:stream{}
有两个特殊的约束:CLASS 和struct.
12.9.2次要约束
一个类型参数可以指定零个或多个次要约束,次要约束代表的事一个借口类型。指定一个借口类型约束时,是向编译器承诺一个指定的类型实参是实现了接口的一个类型。
12.9.3 构造器约束
一个类型参数可以指定零个或者一个构造器约束。指定构造器约束相当于向编译器承诺:一个指定的类型实参是实现了公共无参数器的一个非抽象类型。
class constructorConstraint<T> where T:new()
14.字符串处理注意事项:
1)定义换行:string s=”HI\r\nthere”;一般不建议这样做,相反System.Environment类型定义了一个只读NewLine属性。建议做法:string s = "HI" + Environment.NewLine + "There";
2)若要运行时讲几个字符串连接在一起,请避免使用+操作符,因为它会在堆上穿件多个字符串对象,而堆是需要垃圾回收的,从而影响性能。想反,应尽量使用StringBuilder类型。
14.2.2
string 对象最重要的一个事实是:它是不可变的,也就是说,字符串一经创建不能更改,不能变长,变短,修改其中任何字符。使字符不可变,还意味着在操纵或访问一个字符串时不会发生线程同步问题。
13.2.3
判断字符串相等或对字符串进行排序时,强烈建议调用下列方法之一:
Equals,compare,startweith,endswith.
应该总是区分大小写的比较,原因是假如只是大小写不同的两个字符串被视为相等,那么每次对他们进行排序,他们都可能 按照不同排序排列,从而造成用户迷惑。
如果对大量不同字符串反复执行同一个比较,强烈建议使用System.stringComparer类。
避免使用compareOrdinal及==和!=操作符,之所以避免使用这些方法和操作符,是因为调用者不显式指出以什么方式执行字符串比较,而你无法从方法名看出默认的比较方法,默认情况下,Compareto会执行依赖于语言文化的比较。主要考虑到语言文化的不同。
10. 属性
10.1.1 自动实现属性AIP
如果声明一个属性而不提供get/set方法的实现,C#会自动为你声明一个私有字段。
使用AIP,意味着你已经创建了一个属性,访问该属性的任何代码实际都会调用GET,SET方法。如果以后决定自己实现get或set方法,而不是接受编译器的默认实现,访问属性的任何代码都不必重新编译。如果将name声明为字段,以后又想把它更改为属性,那么访问字段的所有代码都必须重新编译,以便访问属性的方法。
1) 字段声明语法可能包含初始化部分,所以要在一行代码声明并初始化字段。但是没有简单的语法初始化AIP。所以必须在每个构造器中显示初始化每个AIP。
2) 运行时序列化引擎将字段名持久存储到序列化的流中。在任何想要序列化或反序列化的类型中,都不要使用AIP功能。AIP的支持字段的名称是有编译器决定的,而且每次从新编译代码,都可能更改这个名称,任何类型只要含一个AIP,便没办法对该类型的实例进行反序列化了。
3) 进行调试时,不能再AIP的get,set方法添加断点,所以不好检测应用程序在什么时候获取或设置这个属性
AIP是作用于整个属性的,要么都用,要么都不用,不能显示实现一个访问器方法,让另一个自动实现。
10.1.2合理使用属性
1)属性看起来和字段相似,实际是方法。
2)属性可以是只读,只写,字段却可以总是只读只写。
3)属性方法可以抛出异常,字段不会。
4)属性不能作为out ,ref作为参数传递给方法,字段可以。
5)许多人使用属性的一个常见原因是执行线程同步,它可能早晨线程永远终止。因此如果需要线程同步,就不应使用属性,最好使用方法。如果此类可以被远程访问,那么调用属性方法会非常慢。因此在这种下,应该优先使用方法,在个人看来,从MashalByRefObject派生的类永远都不应该使用属性。
6)属性方法可能需要额外的内存,或者返回不正确的引用。
10.1.3 对象和集合初始化器
Employee e=new Employee(){name=”jack”,age=30};
10.1.4 匿名类型
1)var的使用,主要和dynamic的区别。
用var声明局部变量只是一种简化语法,它要求编译器根据一个表达式推断具体的数据类型。Var关键字职能用于声明方法内部的局部变量,而dynamic关键字可用于局部变量、字段和参数。
Var o1=new {name=”jack”,age=30};
Dynamic是运行时解析
//动态使用expandoObject的用法
dynamic e = new System.Dynamic.ExpandoObject();
e.x = "aa"; e.y = "bbb";
//e.z = "xxx";
return e.x.ToString() + e.y + e.z;
2)使用Tuple,支持7个参数的类型
//第一种语法
return new Tuple<int, int>(Math.Max(value1, value2), Math.Min(value1, value2));
//第二中语法
return Tuple.Create(Math.Max(value1, value2), Math.Min(value1, value2));
10.2 有参属性
c#语言把有参属性称为索引器
C#只允许在对象的实例上定义索引器,c#没有提供定义静态索引器属性的语法。
一个类型可定义多个索引器,只要索引器的参数集不同即可。
Class BitArray
{ public bool this[int 32 bitpos]
{}}
第9章 .参数
1)参数默认值的使用
2)out:如果方法的参数用Out来标记,表明不指望调用者在调用方法之前初始化好了对象。被调用的方法不能读取参数的值,而且在返回前必须向这个值写入。想法,如果方法的参数用ref来标记,调用者就必须在调用方法前初始化参数的值,被调用的方法可以读取值以及或者向值写入。
3)可变数量的参数 params关键字告诉编译器向参数应用一个system.paramArrayAttibute的一个实例。
4)dynamic类型,在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查, 改为在运行时解析这些操作。该类型简化了对 COM API(例如 Office Automation API)、动态 API(例如 IronPython 库)和 HTML 文档对象模型 (DOM) 的访问。在大多数情况下, dynamic 类型与 object 类型的行为是一样的。 但是,不会用编译器对包含 dynamic 类型表达式的操作进行解析或类型检查。 编译器将有关该操作信息打包在一起,并且该信息以后用于计算运行时操作。在此过程中,类型 dynamic 的变量会编译到类型 object 的变量中。 因此,类型 dynamic 只在编译时存在,在运行时则不存在。