C#的数据类型以及内存管理机制剖析(2)

来源:互联网 发布:js获取应用上下文 编辑:程序博客网 时间:2024/04/29 13:59

1. Object类再分析:

System.Object是所有.Net类的基类,包括值类型和引用类型。值类型为什么也是继承于System.Object的呢?Object不是引用类型吗?这个就涉及了.Net的一个有趣而神奇的机制--装箱和拆箱(box&un-box)。这个后面会提到。

Object是所有类(class)和结构(struct)的基类。Class都是继承于Object类的,struct都是继承于System.ValueType的,而System.ValueType是继承Object。

好了,既然Object类级别这么高,那么它应该包含哪些成员呢?当然是不会有成员变量了,这东西不可以在那么高级的类里存在的,而且也没有必要。不过Object类定义了很多通用的方法,如果你新建的类没有重载这些方法,那你的新建类就会自动继承Object类的这些方法。我把这些通用方法列于下表,以便大家参考:

方法名 前缀修饰符 作用  
string ToString() public virtual 返回对象的字符串表示  
int GetHashTable() public virtual 在实现字典(散列表)时使用 这是在集合(散列表中)使用的对象
bool Equals(object obj) public virtual 对对象的实例进行相等比较  
bool Equals(object obj1, object obj2) public static 对对象的实例进行相等比较  
bool ReferenceEquals(object obj1, object obj2) public static 比较两个引用是否指向同一个对象  
Type GetType() public 返回对象类型的详细信息  
object MemberwiseClone() protected 进行对象的浅表复制  
void Finalize() protected virtual 析构函数  

在上面我们看到了一些像virtual,static,protected这样的修饰符,我在学习C#语言机制的时候曾经对这些内容做了些详细的整理,请参考《C#修饰符大总结》。

我们看virtual修饰符,这说明某些方法是能够被重载的。比如说ToString()这样的方法。我们自己所定义的类不特别声明的话,都会继承Object。也就是说Object的这些公用方法存在于每一个类中,无论是.Net Framework中的类还是自定义的类。

2. 垃圾回收机制,dispose() & Finalize()

在《C#的数据类型以及内存管理机制剖析(1)》中我提到了.Net的垃圾回收机制。这里在简要地提一下,这个机制真的是.Net核心内容之一,尽管不能够被直接使用,但理解其机制却相当重要。能够在程序中避免很多不必要的错误。GC的流程如下图所示:

GC

我们可以在程序中调用垃圾回收:

view sourceprint?1 System.GC.Collect()

不过请大家小心,并不是调用了这段代码后,垃圾回收就能够回收所有未引用对象。这个也是要分情况的,有时候释放了集合对象,但是集合中的元素并不一定会被释放。这还要看这个元素对象是否也出了代码作用域(代码中表示就是,离开包含该元素的某一层括号)。垃圾回收机制能够很好得管理托管资源和内存,但是在处理一些非托管资源比如文件句柄、网络连接、数据库、特殊设备等,就会有一些问题。

(1).Net析构函数Finalize()

view sourceprint?1 class TestClass 

2 { 

3      ~TestClass() 

4      { 

5           //implementation 

6      } 

7 }

事实上这个析构函数会在生成IL时被编译成下面的形式,会自动得生成Finalize方法:

protescted overide void Finalize()  

{  

    try  

    {  

         //析构函数执行  

     }  

     finally 

      {  

          base.Finalize(); 

      }  

}

析构函数会在GC释放对象之前调用,由于.Net垃圾回收的机制,我们无法控制析构函数的执行的时间,也无法预知析构函数。所以如果我们需要去释放非托管资源,把这个释放的操作放在析构函数Finalize()中会遇到麻烦。主要有几个原因:①我们无法控制析构函数在特定的时候来执行,这样有些重要资源使用完就应该立刻释放的,就不适合放置这里。②也不可能控制Finalize的执行顺序,但某些对象有关联,需要顺序释放的情况也是不能完成的。③析构函数执行,会延迟该对象内存释放的时间。必须要等到第二次GC才有可能完全释放改对象。④运行库用一个线程顺序链式得执行每个需要执行的Finalize方法,这个队列会影响性能。

综上所述,Finalize()是不适合在.Net程序中对一个对象的资源释放和最后处理,同时对执行时间有要求的一些操作。C#推荐使用System.IDisposable来取代这个析构函数。加入Finalize后的GC机制如图所示:

GC2

 

(2)IDisposable接口

IDisposable提供了可以在确定的时机释放未托管资源的功能。其避免了析构函数以为垃圾收集机制而产生的一些问题。在对象结束的时候显式得调用Dispose()方法可以直接做一些释放资源的操作。

view sourceprint?1 Class TestClass:IDisposable 

2 { 

3    public void Dispose() 

4    { 

5       //implementation 

6    }     

7 }

上面是实现IDisposable这个接口,我们为了使一个对象总是能执行Dispose方法,通常我们会把Dispose()放在try/catch的Final段,也就是无论怎样都一定会执行,包括异常情况。假设我们有一个CriticalResource类,那么可以这么做:

CriticalResource Instance = null; 

try 

    Instance = new CriticalResource; 

    //processing code 

finally 

    if(Instance != null)  

      Instance.Dispose(); 

//也可以用下面的方式,使用using块,可以减少代码量,起到同样效果 

using(CriticalResource Instance =  new CriticalResource) 

    //processing 

}
(3)Finalize和Dispose的双重实现
可以将Dispose()作为结束对象的方法,Finalize做为安全机制(没有调用Dispose是作为保险的方式)。这个方式应该是最佳释放资源解决方案,不过比较复杂些,可以参考下面的简单实现。

view sourceprint?01 public class CResource : IDisposable 

02 { 

03    //记录是否已经执行Dispose 

04    Private bool isDispose = false; 

05    //实现Dispose方法 

06    Public void Dispose() 

07    { 

08       //释放资源 

09       Dispose(true); 

10       //GC禁止使用finalize析构函数 

11       GC.SuppressDinalize(this); 

12    } 

13   

14    protected virtual void Dispose(bool disp) 

15    { 

16       //没有执行过Dispose 

17       if(!isDisposed) 

18       { 

19          if(disp) 

20          { 

21             //释放托管资源 

22          } 

23          //释放非托管资源 

24       } 

25       isDisposed = true; 

26    } 

27   

28    ~CResource() 

29     { 

30        Dispose(false) 

31     } 

32   

33    public TestMethod() 

34    { 

35       //确保执行时资源未释放 

36       if(isDisposed) 

37       { 

38          throw new Exception(); 

39       } 

40    } 

41   

42 }