常量和方法
常量
C#中的“常量”有两种,一种是const修饰,一种是readonly修饰,至于他们之间的区别也很简单,const是在编译的时候确定的,即,将const变量的值直接编译放入IL代码中,直接寻址,没有另外创建内存来存储const变量。readonly是指该变量不能被修改,只读变量,只能在构造函数时对其进行赋值(当然可以使用内联初始化),由于这个会另外分配内存存储readonly的值,所以运行时可以通过反射来修改。
const默认是static类型,属于类型级别。当应用程序A调用dll B的时候,B中有const变量,如果修改const变量(程序)之后仅仅编译B的dll,那么应用程序A的结果是不会变的,因为B中const值已经编译成IL的部分了,因此要适应这样的改变同时需要编译应用程序A。
readonly修饰符修饰引用类型时,表示引用是immutable,而并不代表所指向的对象是immutable。
静态构造函数
静态构造函数,一般不会默认生成,需要显式的实现,而且一定是无参数的,而且不能添加访问限制符,因为默认的是private,至于静态函数什么时候执行,分两种情况,一种是precise,另一种是before-field-init。顾名思义,precise就是当第一次创建类型实例或者访问成员的时候,先调用静态函数,而且是每次创建实例的时候都检测是否调用了静态函数;before-field-init是指在访问这些静态成员或者调用构造函数之前的某一个时刻,具体时间不定。
有两种情况,那么CLR是如何决策使用哪一种呢。
当使用内联的方式初始化静态变量的时候(是不是也默认静态构造函数呢,是的,初始化值的时候才会有静态构造函数,否则创建对象的时候会将变量赋值为0/null),使用的是before-field-init方式,而显示实现静态构造函数的时候是用precise方式。
创建对象的4步骤:
1. 分配内存,成员字段(包括基类一直到system.object) + 类型对象指针 + 同步块索引
2. 初始化类型对象指针和同步块索引
3. 初始化所有字段为0/null。
4. 调用构造函数。
值类型不允许内联形式的非静态变量初始化,但是允许内联形式的静态变量初始化。
实例构造函数
引用类型的实例构造函数:
有默认的无参数构造函数,允许默认的内联初始化形式。每当调用一个构造函数构造实例的时候都会将变量初始化定义放入构造函数之前。(CLR VIA C# P207)。而且静态类默认情况下是没有默认实例构造函数的,abstract的默认构造函数是protected的。而且有的时候创建一个对象不一定都得调用构造函数的,比如memberwiseClone函数,通过拷贝的形式构造(类似于c++的拷贝构造函数,而c#中的构造函数为c++的普通构造函数)。
值类型的实例构造函数:
运算符重载和类型转换操作符重载
CLR不清楚运算符和类型转换操作符,都是编译器决定,编译之后将他们生成对应的方法,比如+号经过C#编译之后的方法为op_addition函数,然后元数据维护+和op_addition的对应关系,因此如果用户自定义一个函数op_addition不会和+号冲突。
类型转换操作符分为隐式和显示2种,一般隐式的都是在不丢失精度和数量级的情况下。而显式则可能丢失,因此可能会抛出异常overflowexception等。
重载运算符都是public static的情况,一般至少要有一个是封闭类型(即在Sample类中重载显式转换运算符,那么至少要有一个参数是Sample)。
扩展方法
要定义一个扩展方法,首先要在一个public static类中,扩展方法也必须是static,而且至少要有一个参数,第一个参数必须要用this 来修饰,比如为StringBuilder扩展一个方法,你可以定义为public static void IndexOf(this StringBuilder sb, char value); 那么可以直接通过sb.IndexOf('c')来访问了,这样做的好处有:
1. 这样我们无需明确标出哪个静态类中调用方法,编译器会自己寻找匹配的扩展方法,当然如果引入静态类的命名空间可以提高性能,使编译器更快的找出。
2. 链式表达,可以实现sb.Replace('c', 'b').IndexOf('c')。
当然扩展方法也存在一个问题,就是版本问题,如果.net 某某版本支持StringBuilder的IndexOf方法,那么他会先寻找官方库定义的那个。