对.Net Framework的认识(3)
CLR对类型/结构的字段的排列,可以通过System.Runtime.InteropServices.StructLayoutAttribute来指定,它有三个值,LayoutKind.Auto指CLR自动排列字段;LayoutKind.Sequential指按开发人员声明字段的顺序排列字段;LayoutKind.Explicit表示使用偏移量来排列字段,它要和FieldOffset()一同使用,是精确定位。比如:
[StructLayout(LayoutKind.Explicit)]
struct S1
{
[FieldOffset(0)]
int a;
[FieldOffset(0)]
int b;
}
这样子的话,变量a和b的内存地址相同。
C#中引用类型字段排列属性的默认值是Auto,值类型默认值是Sequential。
静态类不能有实例成员,成员都需要加上static修饰符,不能有实例构造函数,只能有静态构造函数,即构造函数加上static修饰符且不能有访问修饰符和参数,不能实现接口,它是一个abstract和sealed类。
静态构造函数也可以在实例类和struct中声明,它会在类型第一次被访问时被调用,它默认的访问是private。静态构造函数只能操作静态字段,因为它被调用时,还没有任何实例产生,所以无法对任何实例字段进行操作。静态字段如果在声明时被赋值,则该赋值操作在调用静态构造函数之前。
类的成员字段在实例初始化时,如果未被赋值,会被赋null或0。
Struct类型不能有显示的无参数构造函数,它的值类型实例字段声明的时候不能直接赋值,必须通过实例构造函数赋值,且所有声明的值类型实例字段必须在构造器内被全部赋值初始化,但是静态字段是可以在声明时赋值的。
ref关键字指明被传参数为引用类型,该参数可以在方法内被使用,out关键字则指明被传参数为输出结果,不能在方法内被调用,只能作为结果被赋值。其实,out的功能只是ref的一半,ref可输入输出,而out只能输出。另外,定义方法重载时,如果方法签名都一样,其中一个参数前面有ref或者out,一个没有,是可以重载的,但是一个有ref,一个有out就无法重载。对于方法中含有ref和out关键字的参数,在调用方法传入参数时,也需要添加ref和out关键字。
String类型是个引用类型,可以理解成是一个常量,即赋值后无法再更改且保存在元数据中,当你把多个String类型变量通过"+"操作符连接时,其实创建了多个String类型放在元数据中,并且连接会在运行时执行。而你如果直接把多个带双引号的String值通过"+"连接,则连接是在编译时执行,并只创建一个String对象在元数据中。因此应该使用StringBuilder来连接String类型,这样可以避免生成过多的String对象在元数据里。
C#中提供了@符号来声明字符串,它会把所以字符都作为字符串,而无需担心转义字符的问题,比如:
String s="\\"; 和 String s=@"\"; 这两个String值是一样的,都是"\"
Hashtable的实现
1、基于数组,最小容量为11,如果满了,就增大一倍,数据重新计算Index并插入。
2、数据在数组中的Index是根据key的Hash值对数组长度求余获得,如果该Index已经被占用,则通过index+incr去不断搜索后续的index,直到找到空的为止,incr的数值由一个与key的hash值有关的算法算出。
Hashtable的冲突解决方法除了.Net使用的闭散列法(也叫开放地址法),还有开散列法,即对于数组的某个Index,添加诸如链表之类的额外存储空间来解决冲突,这样一个Index可以存储多个数据,查询的时候采用顺序遍历(当然也可以用其他高效查询方法,视具体情况而定)。
.Net的资源分为托管资源和非托管资源,CLR只管托管资源,非托管资源需要开发人员自行维护。
托管资源会存放在两个地方即栈和堆,栈里存放值类型和引用(非对象实例,就是那个引用对象实例的引用),它们不受GC管理,在过了作用域后会自动释放所占内存(由编译器分配和释放)。堆里则存放对象实例,由GC管理。
.Net的GC把内存区域分为3代,即0代,1代和2代,0代是分配新生成对象的,1代则是保存0代回收后仍存在的对象,而2代则是保存1代回收后仍存在的对象。当0代内存被回收后,剩下的对象会被压入1代,同理1代回收后,剩下的对象会被压入2代。另外,如果2代做回收,1代和0代也会做回收,同理如果1代做回收,0代也会做回收。对于大于85000字节的对象,有个专门的堆叫大对象堆来保存,这个对象堆的回收会在2代做回收的时候一起做,但是它和小对象堆做回收的区别在于,对象被回收后的空隙不会被压缩,而是用一张表来记录哪些内存区可以再次被使用,当然这样免不了会有内存碎片,但是目前.Net并未为此做任何处理。
触发GC进行内存回收的情况有
1、每代和大对象堆都有一个内存区域的阙值,可以理解为可使用内存的最大值,如果接近或者超过这个值,就会触发垃圾回收,阙值会动态变化。
2、调用GC.Collect方法。
3、操作系统内存不足,会通知应用调用GC进行回收。
4、GC通过算法认为进行一次回收是非常有效的时候。
.Net的析构函数是不建议使用的,因为无法控制调用它的时间和顺序,可能会导致诡异的异常,同时对象并不是马上被释放,可能会延迟释放的时间,因为它会被压到下个GC代里。对于释放对象可以实现IDisposable接口,然后调用该接口的Dispose方法,通常Dispose方法会被放在finnally块里,以确保会被释放掉。.Net 3.0提供了using关键字,在using块里,会自动调用Dispose方法来释放对象,和finnally+Dispose是一样的作用。