65.9K
CodeProject 正在变化。 阅读更多。
Home

构造函数、析构函数和 MSIL

starIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

1.00/5 (2投票s)

2001年9月19日

2分钟阅读

viewsIcon

50487

downloadIcon

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.FinalizeDispose 的更多信息,请查阅 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 平台时可能会很有用。

© . All rights reserved.