Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is it possible to call Finalize in Dispose?

I was browsing the decompiled source code for a DLL in Reflector, and I came across this C# code:

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

My first reaction was "What? I thought you couldn't call the finalizer manually!"

Note: The base type is object.

To be sure it wasn't a Reflector quirk, I opened up the method in ILSpy. It generated similar code.

I went to Google to confirm my new discovery. I found the documentation for Object.Finalize, and this is what it said:

Every implementation of Finalize in a derived type must call its base type's implementation of Finalize. This is the only case in which application code is allowed to call Finalize.

Now I don't what to think. It might be because the DLL was compiled with C++. (Note: I couldn't find the implementation of Dispose. Maybe it's auto-generated.) It might be a special allowance for the IDisposable.Dispose method. It might be a flaw in both decompilers.

Some observations:

  • I couldn't find the implementation of Dispose in the source code. Maybe it's auto-generated.
  • Reflector shows a method named ~ClassName. It seems as though this method might not actually be the finalizer, but the C++ destructor, or even an ordinary method.

Is this legal C#? If so, what is different about this case? If not, what is actually happening? Is it allowed in C++/CLI, but not C#? Or is it just a glitch in the decompiler?

like image 843
Kendall Frey Avatar asked Aug 21 '12 00:08

Kendall Frey


2 Answers

As the other answerers have noted, you're correct, the reason the disposal code is different is because it's C++/CLI.

C++/CLI uses a different idiom for writing cleanup code.

  • C#: Dispose() and ~ClassName() (the finalizer) both call Dispose(bool).
    • All three methods are written by the developer.
  • C++/CLI: Dispose() and Finalize() both call Dispose(bool), which will call either ~ClassName() or !ClassName() (destructor & finalizer, respectively).
    • ~ClassName() and !ClassName() are written by the developer.
      • As you noted, ~ClassName() is treated differently than in C#. In C++/CLI, it stays as a method named "~ClassName", whereas ~ClassName() in C# gets compiled as protected override void Finalize().
    • Dispose(), Finalize(), and Dispose(bool) are written solely by the compiler. As it does so, the compiler does things that you're not normally supposed to.

To demonstrate, here's a simple C++/CLI class:

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

Here's the output from Reflector, decompiling to C# syntax:

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);
    }
}

Edit

It looks like C++/CLI handles constructor exceptions better than C#.

I wrote test apps in both C++/CLI and C#, which define a Parent class and a Child class, where the Child class's constructor throws an exception. Both classes have debug output from their constructor, dispose method, and finalizer.

In C++/CLI, the compiler wraps the contents of the child constructor in a try/fault block, and calls the parent's Dispose method in fault. (I believe the fault code is executed when the exception is caught by some other try/catch block, as opposed to a catch or finally block where it would be executed immediately, before moving up the stack. But I may be missing a subtlety there.) In C#, there's no implicit catch or fault block, so Parent.Dispose() is never called. Both languages will call both the child & parent finalizers, when the GC gets around to collecting the objects.

Here's a test app I compiled in 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();
}

Output:

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()

Looking at the Reflector output, here's how the C++/CLI compiler compiled the Child constructor (decompiling to C# syntax).

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

For comparison, here's the equivalent program in 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;
}

And the C# output:

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()
like image 67
David Yaw Avatar answered Oct 22 '22 15:10

David Yaw


Yes, you are looking at C++/CLI code. Short from the explicit call to the finalizer, a common C++/CLI pattern, the [MarshalAs] attribute on the argument is a dead giveaway.

C++/CLI works differently from C#, the IDisposable interface and the disposing pattern is completely worked into the language. You never specify the interface name, you also cannot use Dispose directly. A very typical example is a ref class wrapper that wraps an unmanaged C++ class. You can paste it into a C++/CLI class library and look at IL you get from this code:

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" is the native class, the wrapper stores a pointer to it as a private member. The constructor creates the instance with the new operator. Which is the native new operator, the managed one is called gcnew. The ~Wrapper() method declares the "destructor". Which actually is the dispose method. The compiler generates two members, a protected Dispose(bool) member, the one you are looking at in your snippet and probably familiar to you as the implementation of the disposable pattern. And a Dispose() method, you should see it as well. Note that it calls GC.SuppressFinalize() automatically, just as you would do explicitly in a C# program.

The !Wrapper() member is the finalizer, same thing as a C# destructor. Calling it from the destructor is permitted and very often makes sense. It does in this example.

like image 24
Hans Passant Avatar answered Oct 22 '22 13:10

Hans Passant