Finalize,即.NET类型的非确定性析构器,将在对象被GC检查回收前调用。现在我关心一个问题,值类型,一般是自定义struct的析构器会不会被调用呢?一般值类型是在栈上分配,因此就不在托管堆上,不存在被GC回收的情况。但是我们要是将值类型装箱以后放在托管堆里了呢?他的析构器会不会被调用呢?由于C#和C++对析构器语法特别对待,我们不能在struct里面直接重写Finalize方法,因此这个实验只能用VB来做:
Public Structure A
Public i As Integer
Protected Overrides Sub Finalize()
MsgBox("Hi, finalized")
End Sub
End Structure
在这个类里面,我们名目张胆地重写了Finalize,并且没有调用基类的Finalize,幸好VB对这种行为视而不见。首先我们检查是不是真的成功重写了基类的方法,用以下代码测试一下:
Dim x As Object = New A
GetType(Object).GetMethod("Finalize", BindingFlags.NonPublic Or _
BindingFlags.Instance _
).Invoke(x, New Object() {})
结果顺利显示了结果。接下来就进入主题,将该装箱后的对象抛弃在托管堆中,然后调用GC.Collect回收,看看是否能启动Finalize:
Dim x As Object = New A
x = Nothing
GC.Collect()
结果……没有运行!?难道是这段代码无法达成调用Finalize的条件?我将Structure换成Class试试看,结果马上显示Finalize被调用了。这说明Finalize对值类型确实不管用。那么是不是因为CLR内部在某处对值类型自动调用GC.SuppressFinalize之类的方法注销了Finalize呢?那好,我们就多给他注册几个试试:
Dim x As Object = New A
GC.ReRegisterForFinalize(x)
GC.ReRegisterForFinalize(x)
......
x = Nothing
GC.Collect()
结果,不管写了几次GC.ReRegisterForFinalize,在GC回收x的时候仍然没有Finalize运行的踪迹。看来,在CLR内部,值类型的Finalize是有特定规则的,不按照一般类的方式运行。我也没研究CLR的机制,没法再深究这个过程原理,以后有机会要好好看看……
打印 | 张贴于 2005-01-12 13:55:00 | Tag:技术随笔
留言反馈
值类型是在栈上分配的,而栈是后进先出的,不是吗?它只能按照与进栈相反的顺序出栈(被销毁),而不能打破这个顺序被Finalize。
Overrides Sub Finalize()
MyBase.Finalize()
End Sub
End Class
被编译成MSIL后,是这样的:
.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
编译器处理后的Finalize方法把析构函数体放在了try语块中,而在finally语块中调用了基类的Finalize方法。这就确保了基类的析构函数总是可以被调用。
问题是,他怎么做到呢?为值类型专门在CLR中进行了设计,还是值类型的某种特性顺带了这种结果呢?如果值类型用了非托管资源怎么释放呢?
@疯牛涕淌
关于Destructors are invoked automatically, and cannot be invoked explicitly,VB确实允许你直接调用自己的Finalize,就写Me.Finalize就行了……
Destructors are not inherited. Thus, a class has no destructors other than the one, which may be declared in it.者少在VB里这是不对的,Finalize在VB中就是一个普通方法,当然可以继承。
1. Destructors are invoked automatically, and cannot be invoked explicitly.
2. Destructors cannot be overloaded. Thus, a class can have, at most, one destructor.
3. Destructors are not inherited. Thus, a class has no destructors other than the one, which may be declared in it.
4. Destructors cannot be used with structs. They are only used with classes.
5. An instance becomes eligible for destruction when it is no longer possible for any code to use the instance.
6. Execution of the destructor for the instance may occur at any time after the instance becomes eligible for destruction.
7. When an instance is destructed, the destructors in its inheritance chain are called, in order, from most derived to least derived.