C#的数据类型以及内存管理机制剖析(2)
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的流程如下图所示:
我们可以在程序中调用垃圾回收:
System.GC.Collect()
不过请大家小心,并不是调用了这段代码后,垃圾回收就能够回收所有未引用对象。这个也是要分情况的,有时候释放了集合对象,但是集合中的元素并不一定会被释放。这还要看这个元素对象是否也出了代码作用域(代码中表示就是,离开包含该元素的某一层括号)。垃圾回收机制能够很好得管理托管资源和内存,但是在处理一些非托管资源比如文件句柄、网络连接、数据库、特殊设备等,就会有一些问题。
(1).Net析构函数Finalize()
class TestClass { ~TestClass() { //implementation } }
事实上这个析构函数会在生成IL时被编译成下面的形式,会自动得生成Finalize方法:
protescted overide void Finalize() { try { //析构函数执行 } finally { base.Finalize(); } }
析构函数会在GC释放对象之前调用,由于.Net垃圾回收的机制,我们无法控制析构函数的执行的时间,也无法预知析构函数。所以如果我们需要去释放非托管资源,把这个释放的操作放在析构函数Finalize()中会遇到麻烦。主要有几个原因:①我们无法控制析构函数在特定的时候来执行,这样有些重要资源使用完就应该立刻释放的,就不适合放置这里。②也不可能控制Finalize的执行顺序,但某些对象有关联,需要顺序释放的情况也是不能完成的。③析构函数执行,会延迟该对象内存释放的时间。必须要等到第二次GC才有可能完全释放改对象。④运行库用一个线程顺序链式得执行每个需要执行的Finalize方法,这个队列会影响性能。
综上所述,Finalize()是不适合在.Net程序中对一个对象的资源释放和最后处理,同时对执行时间有要求的一些操作。C#推荐使用System.IDisposable来取代这个析构函数。加入Finalize后的GC机制如图所示:
(2)IDisposable接口
IDisposable提供了可以在确定的时机释放未托管资源的功能。其避免了析构函数以为垃圾收集机制而产生的一些问题。在对象结束的时候显式得调用Dispose()方法可以直接做一些释放资源的操作。
Class TestClass:IDisposable { public void Dispose() { //implementation } }
上面是实现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是作为保险的方式)。这个方式应该是最佳释放资源解决方案,不过比较复杂些,可以参考下面的简单实现。
public class CResource : IDisposable { //记录是否已经执行Dispose Private bool isDispose = false; //实现Dispose方法 Public void Dispose() { //释放资源 Dispose(true); //GC禁止使用finalize析构函数 GC.SuppressDinalize(this); } protected virtual void Dispose(bool disp) { //没有执行过Dispose if(!isDisposed) { if(disp) { //释放托管资源 } //释放非托管资源 } isDisposed = true; } ~CResource() { Dispose(false) } public TestMethod() { //确保执行时资源未释放 if(isDisposed) { throw new Exception(); } } }
作者:Rocky Yang
出处:http://www.cnblogs.com/yangjian2006/
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。感谢各位能够访问我的博客!
Email:Rocky的邮箱
个人博客:Rocky & Sky IT技术和管理