1. 溢出 Checked UnChecked
checked打开时,如果发生溢出会抛出异常,Unchecked则不会排除异常。
编译器默认是关闭溢出检查的Unchecked。若要打开溢出检查,使用/Checked+.在VS的项目属性中也可设置开启与否。
也可以给一段代码添加这样的标记。如果这段代码中调用了另外一个方法,这个方法是不受这个标记控制的。
System.Decimal不是基元类型,Checked和Unchecked标记对其无效,如果发生溢出是肯定会抛出异常的。
System。Numberics.BigInteger内部使用UInt32表示任意大的整数,没有上限和下限,永远不会溢出,可能会有内存溢出。
2. 引用类型和值类型
引用类型:
指针.多个引用可指向指向同一个实例.复制时只复制引用.
内存必须从托管堆上分配,包含的值类型字段也在堆上。
堆上分配的每个对象都有一些额外成员,都需要初始化。
对象中的其他字节(为字段而设)总是设为0.
从托管堆上分配对象时,可能强制执行一次垃圾回收。
继承自Object.
值类型:
直接存储的是值本身。复制时复制成员.相互独立.
轻量级类型,不受垃圾回收器的控制。减少了托管堆的压力,减少了进行垃圾回收的次数。
所有的值类型都是隐式密封的,不能被继承。
继承自System。ValueType
C#会确保值类型中的所有字段都初始化为0.但是如果只声明一个值类型而不赋值,编译器会提示使用了可能未赋值的字段.
值类型在参数传递时,传入和传出方法,都是复制的,也就是说在方法内的修改不会影响方法外.所以,在使用值类型传递参数时,要考虑参数的大小,涉及性能问题.
值类型的Equal和GetHashCode存在性能问题,如果自定义值类型,应该重写这两个方法.
值类型有未装箱和已装箱两种形式,引用类型总是处于已装箱.
CLR控制类型中的字段布局
为了提高性能,CLR会按照一种方式排列类型中的字段.System.Runtime.InteropSercives.StruceLayoutAttribute可以设置排列布局.
3. 装箱和拆箱 在实际的代码书写中,应考虑装箱和拆箱操作.
装箱时发生的事情:
1. 在托管堆中分配内存,包括值类型的字段和两个额外的成员.
2. 值类型的字段复制到新分配的堆内存.
3. 返回对象地址.
装箱之后,新对象和原来的值类型之间是没有联系的了.新对象的是由GC回收的.这样,已装箱的值类型的生存期超过了未装箱的值类型的生存期.
System.Collections.Generic.List<T> System.Collections.ArrayList ArrayList.Add(object)前者的效率和性能要高于后者,中间少了许多的装箱和拆箱操作.
拆箱的时候有两个步骤:生成两条IL指令.
1. 拆箱(获取已装箱实例中的字段的地址)
2. 字段复制.
拆箱不是直接将装箱过程倒过来,代价要低许多.
拆箱的时候要先将其转型为未装箱时的值类型.如:
Int32 x = 9;
object y = x;
Int16 z = (Int16)y;这样是会抛出错误的.正确的做法是: Int16 z = (Int16)(Int32)y;
大多数方法进行重载唯一的目的就是减少常用值类型的装箱次数.
由于没装修的值类型没有同步块索引,所以不能使用Monitor类型的各种方法(或者C#的lock)让多个线程对这个实例同步访问.
调用一个值类型虚方法时,值类型不会被装箱.如Equals,GetHashCode,ToString. 调用一个非虚的继承的方法时(GetType)需要进行装箱操作.因为这些方法是Object(GetType)定义的.
4. 对象相等性和同一性
同一性是指看两个引用是否指向同一个对象.Object.ReferenceEquals能够达到此效果.
Object.Equals方法实际实现的是同一性,而不是相等性.
System.ValueType重写了Object的Equals方法,进行的是相等性检查,不是同一性检查.
HashCode
自定义的类型在重写Equals的方法时候,应该同时重写GetHashCode方法.因为哈希码用在Hashtable和Dictionary中用于索引项,要求两个对象相等,必须要具有相同的哈希吗.Dictionary是先获得Key的哈希码值,根据这个值确定存储位置(哈希桶),在根据Key查找的时候,先获得Key 的哈希码,根据哈希码查找(先找到哈希桶,在桶内找到哈希码相同的对象).如果修改了Key对象,集合就再也找不到这个对象了,所以应当先删除,再重新添加.
哈希代码是一个用于在相等测试过程中标识对象的数值。 它还可以作为一个集合中的对象的索引。
.NET Framework 不保证 GetHashCode 方法的默认实现以及它所返回的值在不同版本的 .NET Framework 中是相同的。 因此,在进行哈希运算时,该方法的默认实现不得用作唯一对象标识符。
值类型基类的GetHashCode方法则使用了反射,效率也比较低。Object的GetHashCode方法返回的哈希码能作为全局唯一的标识,只是在对象的生命周期中不会变,被垃圾回收之后,这个哈希吗可能会被分给别的对象.如果一个类型重写了GetHashCode方法,这个ID就不具有唯一性了,可以使用System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode方法获取一个对象唯一性ID,即使重写了GetHashCode方法.
5. dynamic基元类型
代码使用dynamic变量或表达式调用一个成员时,编译器会生成特殊的IL描述这样的操作,称为payload(有效载荷).根据实际类型决定具体执行的操作.
dynamic类型可以隐式转换为其他类型,Object不可以,如果类型不兼容,会抛出InvalidCastException.
dynamic和var是不一样的.var是让编译器根据表达式推断具体的数据类型.dynamic是运行的时候决定类型.
COM,如Excel操作时,excel.Cell[1,1]就是dynamic类型excel.Cell[1,1].value和((Range)excel.Cell[1,1]).Value效果是一样的.
dynamic有时候可以简化反射的代码,直接调用方法的名字,而不用通过反射找到指定名字的方法,然后调用.
但是dynamic类型是需要加载额外的dll的,额外开销是不容忽视的.所以用的时候需要综合考虑.