C# 析构函数解析(译)
2012-04-24 23:07
796 查看
C# 析构函数解析(译)
Chandra Hundigam
2002-06-18
原文地址:
http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx
这篇文章谈了如何理解C#的析构函数的工作原理。我理解你们的疑问:为什么在这么简单的析构函数上花费一篇专门的文章来讲述。
介绍:
这篇文章谈了如何理解C#的析构函数的工作原理。我理解你们的疑问:为什么在这么简单的析构函数上花费一篇专门的文章来讲述。等你读完这篇文章,你将会理解跟C++的析构器相比,C#的析构函数是多么的不同。
简单来说,析构函数是一个成员,它实现那些自毁一个类实例所需要的行为。析构器能保证运行时系统,恢复堆空间,关闭绑定在要删除的类实例上的文件I/O,或者两者都有。为了更好的理解,我将把C++的析构函数和C#的析构函数做个比较。
一般来说,在C++中,析构函数在对象销毁是被调用。在C++中我们可以显式的调用析构函数。而且,对象的销毁顺序跟它们的创建顺序是相反的。所以在C++中,你是可以控制析构函数的。
你可能会想C#是怎么对待析构函数的。在C#中,你永远不能调用析构函数,原因是你没法销毁一个对象。那么谁能控制析构函数呢(在C#中)?它是.NET framework的垃圾收集器(GC).
现在就有问题了:为什么GC控制析构函数呢?而不是我们(程序员)自己呢?
答案是很简单的, 对于释放对象,GC能比我们做的更好。如果让我们来做手动的内存管理,我们将不得不同时注意内存的分配和释放。因此总是有可能你会忘记释放内存。而且,手动内存管理会消耗你的时间,并且是一个复杂的过程。那么就让我们来了解一下为什么C#禁止你写下显示的调用析构函数的代码。
1. 如果我们访问非托管代码,我们经常会忘记去销毁对象。这就会漏掉调用析构函数,被对象占有的内存因而永远得不到释放。
让我们通过这个例子来检查这种情况。下图展示了应用程序XYZ加载一段30字节非托管代码的内存栈的情形。
当程序XYZ结束时,想象一下它忘了销毁一个对象,这个对象有非托管代码,那么将要发生的情况是,程序XYZ的内存释放回到堆中,但是非托管代码仍然在内存中。这就造成内存浪费(内存泄漏)。
2. 如果我们试图释放对象仍在做一些处理,我的意思是对象还是活跃的。
3. 如果我们试图释放的对象已经被释放。
那么,让我们来看看GC将会怎么处理上面的情形:
1. 如果程序结束,GC自动回收程序中对象占有的内存。
2. GC跟踪所有的对象,保证每个对象只被销毁一次。
3. GC保证正被引用的对象不会被销毁。
4. GC只在需要的时候销毁对象。一些必要的时机是内存耗尽或者用户显式调用了System.GC.Collect()方法。
理解垃圾收集器(GC)的全部工作是一个比较大的话题。我会涵盖跟本文主体相关的细节。GC是一个.NET Framework的线程,它会在需要的时候,或者是其他的线程处在挂起状态的时候才运行。首先,GC通过遍历对象内部维护的引用字段,创建一个程序使用的所有对象的列表。然后,它会保证这个列表内部没有循环引用。在这个列表中,GC接着查看所有的对象,把那些有析构函数的对象放到另一个被称为FinalizationList(终结表)的列表
那么现在,GC生成了2个线程,一个是可控表,一个是不可控表(或终结表)。可控对象被一个接一个的从列表中清除掉,同时由这些对象占有的内存也被回收。第二个线程,读终结表,并调用每个对象的终结方法。
让我们看看C#编译器是如何理解析构函数代码的。下面是在VS .NET中创建的一个小的类。我创建了一个叫做class1的类,它有一个构造函数和一个析构函数。
using System;
namespace ConsoleApplication3
{
///<summary>
/// Summary description for Class1.
///</summary>
class Class1
{
public Class1()
{}
~Class1()
{}
static voidMain(string[] args)
{
//
// TODO: Add code to start application here
//
Class1 c= new Class1();
}
}
}
编译完代码后,在ILDASM.EXE(微软的反汇编工具)中打开汇编文件,查看IL代码。你将会看到一些不寻常的代码。你会看到编译器自动把析构函数翻译成了一个从Object.Finalize()重写的方法。换句话说,编译器把下面的析构函数:
class Class1
{
~Class1(){}
}
转换为:
编译器生成的Finalize方法包含了在try块中的析构函数,并紧接着一个调用基类的Finalize方法的finally块。这就保证了析构函数总是调用了其基类的析构函数。从这里我们的结论是,在C#中Finallize是析构函数的别名。
需要记住的知识点:
1. 析构函数自动被调用,而且不能被显式调用。
2. 析构函数不能被重载。也就是说,一个类最多只能有一个析构函数。
3. 析构函数不会继承。因此,一个类要么没有析构函数,要么有一个在内部生命的析构函数。
4. 不能在结构体(struct)中定义析构函数。只能在类中定义析构函数。
5. 一个实例,如果它不再可能被任何代码使用,那么它就可能被析构。
6. 析构动作可能在任何时候被执行,只要它满足条件5。
7. 当一个实例被析构了,那么它的继承链上的地析构函数也会被顺序调用,顺序是从最深层到最近层。
Chandra Hundigam
2002-06-18
原文地址:
http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx
这篇文章谈了如何理解C#的析构函数的工作原理。我理解你们的疑问:为什么在这么简单的析构函数上花费一篇专门的文章来讲述。
介绍:
这篇文章谈了如何理解C#的析构函数的工作原理。我理解你们的疑问:为什么在这么简单的析构函数上花费一篇专门的文章来讲述。等你读完这篇文章,你将会理解跟C++的析构器相比,C#的析构函数是多么的不同。
简单来说,析构函数是一个成员,它实现那些自毁一个类实例所需要的行为。析构器能保证运行时系统,恢复堆空间,关闭绑定在要删除的类实例上的文件I/O,或者两者都有。为了更好的理解,我将把C++的析构函数和C#的析构函数做个比较。
一般来说,在C++中,析构函数在对象销毁是被调用。在C++中我们可以显式的调用析构函数。而且,对象的销毁顺序跟它们的创建顺序是相反的。所以在C++中,你是可以控制析构函数的。
你可能会想C#是怎么对待析构函数的。在C#中,你永远不能调用析构函数,原因是你没法销毁一个对象。那么谁能控制析构函数呢(在C#中)?它是.NET framework的垃圾收集器(GC).
现在就有问题了:为什么GC控制析构函数呢?而不是我们(程序员)自己呢?
答案是很简单的, 对于释放对象,GC能比我们做的更好。如果让我们来做手动的内存管理,我们将不得不同时注意内存的分配和释放。因此总是有可能你会忘记释放内存。而且,手动内存管理会消耗你的时间,并且是一个复杂的过程。那么就让我们来了解一下为什么C#禁止你写下显示的调用析构函数的代码。
1. 如果我们访问非托管代码,我们经常会忘记去销毁对象。这就会漏掉调用析构函数,被对象占有的内存因而永远得不到释放。
让我们通过这个例子来检查这种情况。下图展示了应用程序XYZ加载一段30字节非托管代码的内存栈的情形。
当程序XYZ结束时,想象一下它忘了销毁一个对象,这个对象有非托管代码,那么将要发生的情况是,程序XYZ的内存释放回到堆中,但是非托管代码仍然在内存中。这就造成内存浪费(内存泄漏)。
2. 如果我们试图释放对象仍在做一些处理,我的意思是对象还是活跃的。
3. 如果我们试图释放的对象已经被释放。
那么,让我们来看看GC将会怎么处理上面的情形:
1. 如果程序结束,GC自动回收程序中对象占有的内存。
2. GC跟踪所有的对象,保证每个对象只被销毁一次。
3. GC保证正被引用的对象不会被销毁。
4. GC只在需要的时候销毁对象。一些必要的时机是内存耗尽或者用户显式调用了System.GC.Collect()方法。
理解垃圾收集器(GC)的全部工作是一个比较大的话题。我会涵盖跟本文主体相关的细节。GC是一个.NET Framework的线程,它会在需要的时候,或者是其他的线程处在挂起状态的时候才运行。首先,GC通过遍历对象内部维护的引用字段,创建一个程序使用的所有对象的列表。然后,它会保证这个列表内部没有循环引用。在这个列表中,GC接着查看所有的对象,把那些有析构函数的对象放到另一个被称为FinalizationList(终结表)的列表
那么现在,GC生成了2个线程,一个是可控表,一个是不可控表(或终结表)。可控对象被一个接一个的从列表中清除掉,同时由这些对象占有的内存也被回收。第二个线程,读终结表,并调用每个对象的终结方法。
让我们看看C#编译器是如何理解析构函数代码的。下面是在VS .NET中创建的一个小的类。我创建了一个叫做class1的类,它有一个构造函数和一个析构函数。
using System;
namespace ConsoleApplication3
{
///<summary>
/// Summary description for Class1.
///</summary>
class Class1
{
public Class1()
{}
~Class1()
{}
static voidMain(string[] args)
{
//
// TODO: Add code to start application here
//
Class1 c= new Class1();
}
}
}
编译完代码后,在ILDASM.EXE(微软的反汇编工具)中打开汇编文件,查看IL代码。你将会看到一些不寻常的代码。你会看到编译器自动把析构函数翻译成了一个从Object.Finalize()重写的方法。换句话说,编译器把下面的析构函数:
class Class1
{
~Class1(){}
}
转换为:
In Source Code Format: | In IL Code Format: |
class Class1 { Protected override void Finalize() { try{..} finally { base.Finalize();} } } | .method family hidebysig virtual instance void Finalize() cil managed { // Code size 10 (0xa) .maxstack 1 .try { IL_0000: leave.s IL_0009 } // end .try finally { IL_0002: ldarg.0 IL_0003: call instance void [mscorlib]System.Object::Finalize() IL_0008: endfinally } // end handler IL_0009: ret } // end of method Class1::Finalize |
需要记住的知识点:
1. 析构函数自动被调用,而且不能被显式调用。
2. 析构函数不能被重载。也就是说,一个类最多只能有一个析构函数。
3. 析构函数不会继承。因此,一个类要么没有析构函数,要么有一个在内部生命的析构函数。
4. 不能在结构体(struct)中定义析构函数。只能在类中定义析构函数。
5. 一个实例,如果它不再可能被任何代码使用,那么它就可能被析构。
6. 析构动作可能在任何时候被执行,只要它满足条件5。
7. 当一个实例被析构了,那么它的继承链上的地析构函数也会被顺序调用,顺序是从最深层到最近层。
相关文章推荐
- 【转】C#类似Jquery的html解析类HtmlAgilityPack基础类介绍及运用
- 【转】C#事件(event)解析
- [C#] 隐式类型var —— 示例解析
- 开源Math.NET基础数学类库使用(03)C#解析Matlab的mat格式
- 【技术原创】京东商城价格图片分析解析源代码下载(C#),附演示程序 转
- 关于c#解析JSON格式在.netframeword 3.5以下版本的原始方法
- MODBUS协议解析中常用的转换帮助类(C#)
- C#事件(event)解析(转)
- C++转向C# 的疑惑:析构函数及相关
- C# 串口操作系列(3) -- 协议篇,二进制协议数据解析
- C#使用Tesseract OCR 解析验证码
- 解析C++中虚析构函数的作用
- C# 串口操作系列(3) -- 协议篇,二进制协议数据解析
- 遇到的问题--------C#连接数据库的语句带特殊字符时无法解析
- Unity3D研究院之使用 C#合成解析XML与JSON(四十一)
- C#解析HTML
- C#下解析HTML的两种方法介绍
- C#实现DNS解析服务
- C# 析构函数
- C#基础解析之Ⅲ 【循环结构】