我勒个去……过了整整仨月,才看完一章,我是有多懒了。。。。不过最近工作忙,而且新房还装修。一体检还查出个过度疲劳。似乎卖苦肉计也没啥意思哈。。。。以后尽量勤奋点吧!嗯啊~
 
Chapter 2 .NET Resource Management
 
     GC通过定义“代”的概念来优化回收工作。可以筛选出更加有“潜质”的回收对象。所有自上次GC以后创建的对象是第0代。经过一次GC后仍存在的,升级为第1代。再经过一次GC后仍存在的升级为第2代(最高)。每一次GC都会检查第0代的对象,大约10次GC才会检测一次第1代的,而大约100次GC才会检测一次第2代的对象。这样设计,是为了将局部变量快速地遍历回收,同时保证一些全局的一直被使用的对象不每次都被遍历。
     在C#中,使用Finalizer来释放资源并不是一个好主意。因为Finalizer只有在对象被GC时才被调用。所以被调用的时机是未知的。
     存在Finalizer的对象,必须要等到下一个GC周期才能被析构。因为Finalizer不能在GC的线程中被调用。GC会将存在Finalizer的对象存入一个队列,再开一个线程给他们。所以Finalizer会使本可以被迅速回收的局部变量的代数迅速升级。
  • Prefer Member Initializer To  Assignment Statements
    • 随着类的构造函数的增多,可能会造成一些成员变量没有被同步地初始化的情况。避免这种情况的一个解决办法就是在声明它们时就初始化。编译器会将声明时初始化编译为所有构造函数方法体之前的一段初始化代码,而且是按照你定义的顺序。这样保证构造函数中所有声明初始化已经完成。
    • 但是,当你需要给变量初始化为0或者null时,不要使用声明初始化。如果不使用,系统会在较低的level为这片内存区域设置为0或者null。这是非常高效的。反之,编译器需要在代码中加入额外的赋值指令。这样做不错,只是效率不如前者高。
    • 当你需要在构造函数中根据参数初始化一些成员变量时,不要使用声明初始化。因为这样声明初始化的对象生成后直接就被GC掉。
    • 当你需要捕捉初始化的异常时,不要使用声明初始化。因为它无法放在try...catch中。
  • Use Proper Initialization For Static Class Member
    • 静态构造函数在对应的类型第一次访问之前被调用。所以可以用来进行需要一些逻辑的静态初始化。例如:异常捕获。
  • Utilize using and try/finalize for Resource Cleanup
    • 所有含有非托管资源的类型都实现的IDisposable接口。应当显示调用他们的Dispose()方法来释放内存。
    • 当对象抛出异常时,Dispose()可能没有被调用。最简单的解决方法是使用using将非托管对象包围起来。using会在编译时生成try/finalize语句块。
    • using(){}中的对象类型必须是实现了IDisposable接口的。如果不是,可以用as骗过编译器。但效果相当于using(null){},不会有任何意义。
    • 当有多个非托管对象同时出现时,可以自己手写try/finalize语句块。嵌套using()也成,只是结构会比较难看。。。
    • 也可以用Close()来释放非托管资源。但与Dispose()不同的时,Close()在释放资源后不会将它从GC的Finalize Queue中移除。所以Dispose()比Close()更好。
    • Dispose()不会将对象从内存移除,只是让对象释放非托管资源,例如数据库连接。这时,这块内存还在,但是数据库连接已经断开了。
  • Avoid Creating Unnecessary Objects
    • 作者最想说的就是不要在方法里面创建引用类型的对象,尤其是频繁调用的方法。如何避免呢,有下面三个方法。
    • 把一直重复使用的对象提升为全局对象,避免每次都创建。
    • 一些常量的对象写成静态属性。只在获取时创建。
    • 对于一些不可变的对象,实现一个可以构造器。例如string和StringBuilder。
  • Implement the Standard Dispose Pattern
    • 实现你自己的Dispose模式时,一定要写Finalizer,虽然会有一些性能损耗,但可以保证非托管资源一定能被释放。
    • 实现Dispose模式需要做4件事:
      • 释放非托管资源
      • 释放托管资源
      • 设置一个标志位,标明这个对象已经被Dispose。其它方法被调用时,抛出ObjectDisposedException。
      • 触发Finalization。
    • 只在需要的时候写Finalization,否则会造成性能开销。
    • 永远不要在Finalizer和Dispose中做释放资源以外的事情。
    • 可以写一个Helper method:void Dispose(bool isDisposing)。
public class MyResourceHog : IDisposable
{
     private bool alreadyDisposed = false;
     public void Dispose()
     {
          Dispose(true);
          GC.SuppressFinalize(this);
     }
     protected virtual void Dispose(bool isDisposing)
     {
          if (alreadyDisposed)
               return;
          if (isDisposing)
          {
               // elided: free managed resources here.
          }
          // elided: free unmanaged resources here.
          alreadyDisposed = true;
     }
     public void ExampleMethod()
     {
          if (alreadyDisposed)
               throw new ObjectDisposedException(
                    "MyResourceHog",
                    "Called Example Method on Disposed object");
          // remainder elided.
     }
}     
  • Distinguish between Value Types and Reference Types
    • 值类型一般用于存储数据,而引用类型一般用于包含行为、实现继承和多态。
    • 决定一个自定义的类型是值类型还是引用类型很重要。将一个引用类型改为值类型会带来深远的影响。值类型将不再支持继承和多态,而且作为参数或返回值传递时,会产生一个新的对象。
    • 如果拿不定主意,选择引用类型。
  • Ensure 0 is a Valid State for Value Types
    • 所有的值类型都会在初始化时将内部的值置为0,这是C#语言的特性,你无法改变,你只能去适应。
    • 对于enum,最好将0作为一个合理的值。否则EnumType aaa = new EnumType();这样的语句会造成一个无效的enum值。如果是作为位操作使用,请将0作为None使用,即每一种情况都没有选中。
  • Prefer Immutable Atomic Value Types
    • 不可变的数据类型更易于维护。然而创建不可变数据并不容易。防止struct中的字段被滥改,可以使用属性加以控制。但是过多的属性也带来过多的隐患。可以将属性的set设为private。只在构造函数中进行赋值。如果更严格一点,可以将字段设为readonly,并且去掉属性的set。
    • 对于struct中的引用类型要加倍小心。它们可能从外部被改变。保险的解决方案是在构造函数中进行复制,或者在属性的get时进行复制。
    • 其它的防止struct中的字段被改变的方式两个。一个是使用工厂方法。还有就是将多部操作进行封装来构造不可变数据,例如StringBuilder和string。