什么时候可以在Dispose中调用Finalize?

时间:2012-08-21 00:03:21

标签: c# .net c++-cli idisposable finalizer

我在Reflector中浏览反编译DLL的反编译源代码,我遇到了这个C#代码:

protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
{
    if (flag1)
    {
        this.~ClassName();
    }
    else
    {
        base.Finalize();
    }
}

我的第一反应是“什么?我以为你无法手动调用终结器!”

注意:基本类型为object

为了确保它不是Reflector的怪癖,我在ILSpy中打开了方法。它生成了类似的代码。

我去谷歌确认我的新发现。我找到了Object.Finalize的文档,这就是它所说的:

  

派生类型中Finalize的每个实现都必须调用其基类型的Finalize实现。 这是唯一允许应用程序代码调用Finalize的情况。

现在我没有想到什么。可能是因为DLL是用C ++编译的。 (注意:我找不到Dispose的实现。也许它是自动生成的。)它可能是IDisposable.Dispose方法的特殊限制。这可能是两个反编译器中的一个缺陷。

一些观察结果:

  • 我在源代码中找不到Dispose的实现。也许它是自动生成的。
  • Reflector显示名为~ClassName的方法。似乎这个方法实际上可能不是终结器,而是C ++析构函数,甚至是普通的方法。

这是合法的C#吗?如果是这样,这个案子有什么不同?如果没有,实际发生了什么?是否允许在C ++ / CLI中使用,而不是C#?或者它只是反编译器中的一个小故障?

2 个答案:

答案 0 :(得分:5)

正如其他回答者所说,你是对的,处理代码不同的原因是因为它是C ++ / CLI。

C ++ / CLI使用不同的习惯用法来编写清理代码。

  • C#:Dispose()和~ClassName()(终结器)都调用Dispose(bool)。
    • 这三种方法都是由开发人员编写的。
  • C ++ / CLI:Dispose()和Finalize()都调用Dispose(bool),它将调用~ClassName()或!ClassName()(分别为析构函数和终结器)。
    • ~ClassName()和!ClassName()由开发人员编写。
      • 如前所述,~ClassName()的处理方式与C#中的处理方式不同。在C ++ / CLI中,它保留为名为“~ClassName”的方法,而C#中的~ClassName()则编译为protected override void Finalize()
    • Dispose(),Finalize()和Dispose(bool)仅由编译器编写。正如它所做的那样,编译器会执行您通常不应该执行的操作。

为了演示,这是一个简单的C ++ / CLI类:

public ref class TestClass
{
    ~TestClass() { Debug::WriteLine("Disposed"); }
    !TestClass() { Debug::WriteLine("Finalized"); }
};

这是Reflector的输出,反编译为C#语法:

public class TestClass : IDisposable
{
    private void !TestClass() { Debug.WriteLine("Finalized"); }
    private void ~TestClass() { Debug.WriteLine("Disposed"); }

    public sealed override void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    [HandleProcessCorruptedStateExceptions]
    protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool disposing)
    {
        if (disposing)
        {
            this.~TestClass();
        }
        else
        {
            try
            {
                this.!TestClass();
            }
            finally
            {
                base.Finalize();
            }
        }
    }

    protected override void Finalize()
    {
        this.Dispose(false);
    }
}

修改

看起来C ++ / CLI比C#更好地处理构造函数异常。

我在C ++ / CLI和C#中编写了测试应用程序,它们定义了一个Parent类和一个Child类,其中Child类的构造函数抛出异常。这两个类都有来自构造函数,dispose方法和终结器的调试输出。

在C ++ / CLI中,编译器将子构造函数的内容包装在try / fault块中,并在fault中调用父级的Dispose方法。 (我相信当异常被其他一些try / catch块捕获时,执行错误代码,而不是在向上移动堆栈之前立即执行的catch或finally块。但我可能会在那里错过一个微妙的。)在C#中,没有隐式catch或fault块,因此永远不会调用Parent.Dispose()。这两种语言都会称为儿童和儿童。当GC到处收集对象时,父终结器。

这是我在C ++ / CLI中编译的测试应用程序:

public ref class Parent
{
public:
    Parent() { Debug::WriteLine("Parent()"); }
    ~Parent() { Debug::WriteLine("~Parent()"); }
    !Parent() { Debug::WriteLine("!Parent()"); }
};

public ref class Child : public Parent
{
public:
    Child() { Debug::WriteLine("Child()"); throw gcnew Exception(); }
    ~Child() { Debug::WriteLine("~Child()"); }
    !Child() { Debug::WriteLine("!Child()"); }
};

try
{
    Object^ o = gcnew Child();
}
catch(Exception^ e)
{
    Debug::WriteLine("Exception Caught");
    Debug::WriteLine("GC::Collect()");
    GC::Collect();
    Debug::WriteLine("GC::WaitForPendingFinalizers()");
    GC::WaitForPendingFinalizers();
    Debug::WriteLine("GC::Collect()");
    GC::Collect();
}

输出:

Parent()
Child()
A first chance exception of type 'System.Exception' occurred in CppCLI-DisposeTest.exe
~Parent()
Exception Caught
GC::Collect()
GC::WaitForPendingFinalizers()
!Child()
!Parent()
GC::Collect()

查看Reflector输出,以下是C ++ / CLI编译器如何编译Child构造函数(反编译为C#语法)。

public Child()
{
    try
    {
        Debug.WriteLine("Child()");
        throw new Exception();
    }
    fault
    {
        base.Dispose(true);
    }
}

为了比较,这是C#中的等效程序。

public class Parent : IDisposable
{
    public Parent() { Debug.WriteLine("Parent()"); }
    public virtual void Dispose() { Debug.WriteLine("Parent.Dispose()"); }
    ~Parent() { Debug.WriteLine("~Parent()"); }
}

public class Child : Parent
{
    public Child() { Debug.WriteLine("Child()"); throw new Exception(); }
    public override void Dispose() { Debug.WriteLine("Child.Dispose()"); }
    ~Child() { Debug.WriteLine("~Child()"); }
}

try
{
    Object o = new Child();
}
catch (Exception e)
{
    Debug.WriteLine("Exception Caught");
    Debug.WriteLine("GC::Collect()");
    GC.Collect();
    Debug.WriteLine("GC::WaitForPendingFinalizers()");
    GC.WaitForPendingFinalizers();
    Debug.WriteLine("GC::Collect()");
    GC.Collect();
    return;
}

C#输出:

Parent()
Child()
A first chance exception of type 'System.Exception' occurred in CSharp-DisposeTest.exe
Exception Caught
GC::Collect()
GC::WaitForPendingFinalizers()
~Child()
~Parent()
GC::Collect()

答案 1 :(得分:1)

是的,您正在查看C ++ / CLI代码。从显式调用终结器(一种常见的C ++ / CLI模式)开始,参数的[MarshalAs]属性是一个死的赠品。

C ++ / CLI的工作方式与C#不同,IDisposable接口和处理模式完全适用于该语言。您从不指定接口名称,也不能直接使用Dispose。一个非常典型的例子是一个包装非托管C ++类的ref类包装器。您可以将其粘贴到C ++ / CLI类库中,并查看从此代码中获得的IL:

using namespace System;

#pragma managed(push, off)
class Example {};
#pragma managed(pop)

public ref class Wrapper {
private:
    Example* native;
public:
    Wrapper() : native(new Example) {}
    ~Wrapper() { this->!Wrapper(); }
    !Wrapper() { delete native; native = nullptr; }
};

“Example”是本机类,包装器将指针存储为私有成员。构造函数使用 new 运算符创建实例。哪个是本机新运算符,托管的运算符称为 gcnew 。 ~Wrapper()方法声明了“析构函数”。实际上是处理方法。编译器生成两个成员,一个受保护的Dispose(bool)成员,您在片段中查看的成员,并且您可能熟悉一次性模式的实现。和Dispose()方法一样,你也应该看到它。请注意,它会自动调用GC.SuppressFinalize(),就像在C#程序中显式执行一样。

!Wrapper()成员是终结者,与C#析构函数相同。允许从析构函数调用它并且通常是有意义的。它在这个例子中。