创建引用类型的实例时的步骤:
首先,为实例的数据字段分配内存;
接着,初始化对象的系统开销字段(类型对象指针和同步块索引);
最后,调用类型的实例构造器设置对象的初始状态。
ctor不能被继承,不能用virtual,new,override,sealed,abstract。
如果类中没有显示定义任何ctor,则默认定义一个无参ctor,这个ctor不执行任何语句,只是调用基类的无参ctor;当然,如果类中有ctor,则不存在这个默认的无参ctor。
如果类为abstract的,则默认的ctor是protected;否则这个默认ctor都是public的。
如果类为static的,则不会生成默认的ctor。
如果基类A中没有提供无参ctor,这里,我们只考虑A中只有一个有参ctor(如果连这个ctor也没有,那么A就是上面的那种情况了),这时,子类B必须显示调用基类的ctor,否则不能编译,如下所示:
















最终,都会调用到System.Object的公有无参ctor。
不需要实例构造器的时候:反序列化;Object.MemberwiseClone()方法。
内联方法,其实就是在默认无参ctor中赋值。
2.实例构造器ctor(值类型)
1.C#中,struct不能包含显示的无参ctor——CLR允许struct有无参ctor
2.struct一定要显示调用其有参ctor,这样在new新对象的时候,才会执行ctor中的语句
3.CLR会保证所有实例字段(值类型和引用类型)都被初始化为0或者null (如果没有手动设置,就由CLR自动分配)。
4.在struct中,不能使用内联直接给字段赋值(初始化只能在显示ctor中做)
5.每个字段都要在ctor中初始化,不然编译器会报错
3.静态构造器cctor
cctor可以用于接口/引用类型/值类型,但是C#不支持接口。
cctor位于AppDomain中,在类第一次被访问时执行。
cctor不能超过一个(可以没有),而且永远没有参数。
cctor是私有的,但不能显示声明为私有。
在值类型中定义cctor是没有意义的——没有机会执行cctor。
cctor的调用过程:
编译时,JIT编译器将检查AppDomain是否执行了cctor(有记录的),以决定是否生成调用代码;
执行时,如果有多个线程,则需要一个互斥的线程同步锁,以确保只执行一次cctor。
避免在两个类的cctor中互相引用,CLR不能确保先执行哪一个cctor,可以编译,但是返回指不唯一。
如果cctor抛出异常,CLR会认为该类型不可用,与此类型相关的操作都会抛出System.TypeInitializationException异常。
cctor中只能访问静态字段,它的用途就是初始化这些静态字段。也可以使用内联初始化,与cctor效果一样。
cctor不应调用其基类的cctor,两者没有关系。
CLR不支持静态的Finalize()方法。
cctor的性能:
精确语义Precise:针对在cctor中初始化而言;
字段初始化前语义BeforeFieldInit:针对于内联初始化而言。
二者的区别在于是否会在MSIL中的cctor上附加一个BeforeFieldInit标记。
测试代码如下:














































*Stopwatch类,提供一组方法和属性,可以准确地测量运行时间
4.操作符重载
CLR不知道操作符是什么;操作符是C#定义的,相应的生成编译器识别的语言。
操作符重载是public static的,有一元重载和二元操作两种方式。
要求重载方法的参数至少有一个参数与重载方法的类型一样。
运算符参数不能使用ref/out修饰符。
例子:对于运算符+,
在定义时,会在编译器中生成op_Addition()方法,在方法定义表中,这个方法属于specialname组——说明它是一个特殊方法
在调用时,编译器会在specialname组查找相应的op_Addition()方法,如果存在而且方法参数匹配,则执行;否则,编译错误。
对于操作符重载,建议同时定义一个友好的方法,如重载+,同时定义一个Add方法。
操作符语法详见http://www.cnblogs.com/Jax/archive/2007/09/13/891984.html
5.转换操作符方法
System.Decimal是一个很好的学习操作符重载/转换的Sample
为一个Rational定义转换符构造器和方法:
操作符转换也是public static的














































由上面代码可以看出,implicit/explicit是对两个构造器和两个ToXXX()方法的包装。
相应的IL代码:




6.通过引用向方法传递参数
默认CLR的方法参数都是按值传递的。无论引用还是值类型参数,都是传递一个copy的副本——这样意味着方法可以修改对象,而不对方法外的对象有影响,仅在方法内部有影响。
CLR允许按照引用方式传递参数:out,ref,在声明和调用时都要加上ref/out关键字
二者在CLR中等效,在C#中有区别:
不能将未初始化的参数作为ref传递到方法
























在out方法中必须给out参数初始化赋值,这时,无论调用前是否给out参数初始化赋值,out方法中都要赋值,否则会编译错误。示例如下:



























方法可以基于ref/out可以重载,ref/out也算是方法签名的一部分。但是重载不能仅限于ref和out的差别,如下:
可以有











用ref实现交换两个引用类型的方法:标准写法,使用泛型














out/ref在值类型上使用和引用类型上使用行为相同。仅差在值类型实例分配到的是内存,引用类型分配到的是指针。
以后的FileStream可以写成以下模式:































7.向方法传递可变数量的参数 params关键字
只有最后一个参数可以是params的,而且必须是一个一维数组,可以传递null或0个元素的数组,甚至是不传值(忽略此参数,会默认生成0长的数组作为方法参数)。
调用的时候,不用创建数组对象,直接在方法中传入任意数量的参数。












8.声明方法的参数类型
原则1:方法参数尽可能指定为最弱的类型
选用IEnumberable<T>,而不是IList<T>或ICollection<T>
原则2:方法返回类型尽可能指定为最强的类型
返回FileStream,而不是Stream
原则3:如果希望在不影响调用代码的情况下,能对方法内部实现进行修改,这时候返回类型要使用最弱类型中的最强的一个。
使用IList<T> ,而不是List<T>
9.CLR不支持常量方法和常量参数