构造函数、析构函数和 MSIL





1.00/5 (2投票s)
2001年9月19日
2分钟阅读

50487

2
使用 ILDASM 查看 C# 中的自定义析构函数。
引言
我发现 C# 是一种非常有趣的语言,并且分析 C# 代码生成的 MSIL 代码对我来说很有用。
为了检查析构函数并了解如何编写自定义析构函数,我们将从命令行编译以下代码
class makecall { void print() { System.Console.WriteLine("Executing print."); throw new System.NotImplementedException(); } ~makecall() { print(); } }
class test { public static void Main() { makecall obj=new makecall(); obj=null; System.GC.Collect(); System.GC.WaitForPendingFinalizers(); } }
这与 C++ 非常相似。
关于 GC 的一些说明:如果我们注释掉这些行
System.GC.Collect(); System.GC.WaitForPendingFinalizers();
并重新为 obj
分配一个新值(在末尾插入以下代码)
obj=new makecall();
在执行过程中,我们会注意到两个构造函数都被执行了,但只有在程序结束时,两个析构函数才会被调用。这个想法来自一个著名的网站“如何编写不可维护的代码”中的关于变量名回收的章节。当然,需要一个“嘈杂”的构造函数才能看到这一点。例如:
public makecall ()
{
System.Console.WriteLine("New instance created.");
}
取消注释 GC 行,您将看到正确的执行顺序:先构造函数,然后是关联的析构函数。
最后,我们可以查看此代码的汇编程序版本。将第一个版本发送到反汇编器 ILDASM.EXE,您会注意到析构函数有一个 catch-finally 附加项。例如:
.method family hidebysig virtual instance void Finalize() cil managed { // Code size 20 (0x14) .maxstack 8 .try { IL_0000: ldstr "Instance destroyed." IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: leave.s IL_0013 } // end .try finally { IL_000c: ldarg.0 IL_000d: call instance void [mscorlib]System.Object::Finalize() IL_0012: endfinally } // end handler IL_0013: ret } // end of method Recycle::Finalize
ILASM 不信任我们编写自定义析构函数的能力!
有关 GC、Object.Finalize
和 Dispose
的更多信息,请查阅 Bobby Schmidt 在 MSDN .NET 文档中的文章“Gozer the Destructor”。当然,您需要编译代码并反汇编 exe 文件。在那里您还可以看到有关 System.IDisposable
的信息(另一种与 GC 合作的方式)。
但我的目标不是回顾那篇文章并重复已经写好的内容。我想向您展示如何插入一个 catch
块并根据您自己的需求处理异常。我们为什么要这样做?如果我们正在编写一个组件,并且要对该组件及其使用它的应用程序进行测试,我们需要记录错误、警告和其他有用的信息。标准的质量保证流程。
为了实现一个 catch
,我们将像在 C# 代码中一样做。如果我们在处理异常,它将位于堆栈的顶部。代码如下
.assembly extern mscorlib{} .assembly 'custom'{} .class private auto ansi beforefieldinit makecall extends [mscorlib]System.Object { .method private hidebysig instance void print() cil managed { ldstr "Executing print." call void [mscorlib]System.Console::WriteLine(string) newobj instance void [mscorlib]System.NotImplementedException::.ctor() throw } .method family hidebysig virtual instance void Finalize() cil managed { .try { .try { ldarg.0 call instance void makecall::print() leave.s ex } catch [mscorlib]System.Exception { callvirt instance string [mscorlib]System.Exception::get_Message() call void [mscorlib]System.Console::WriteLine(string) leave.s ex } } finally { ldarg.0 call instance void [mscorlib]System.Object::Finalize() endfinally } ex: ret } .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { ldarg.0 call instance void [mscorlib]System.Object::.ctor() ret } } .method public static void Main() cil managed { .entrypoint newobj instance void makecall::.ctor() pop call void [mscorlib]System.GC::Collect() call void [mscorlib]System.GC::WaitForPendingFinalizers() ret }
请注意嵌套的 try 块。一如既往,如果您小心不要破坏堆栈平衡,MSIL 会非常友好和乐于助人。您可能更喜欢将错误记录到文件。提示:声明一个静态 void,您将在其中进行日志记录,并将错误描述作为字符串传递给它。尝试一下,这并不难,并且在您未来使用 .NET 平台时可能会很有用。