When is it possible to call Finalize in Dispose?
Asked Answered
S

2

10

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?

Swenson answered 21/8, 2012 at 0:3 Comment(2)
What does !ClassName refer to?Swenson
!ClassName is the finalize methods.Spaceband
L
6

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()
Lockridge answered 21/8, 2012 at 0:45 Comment(5)
Out of curiosity, does C++/CLI handle decently the scenario where the constructor of a type derived from an IDisposable base type throws an exception? One of my major peeves with vb.net and C# is that it's extremely difficult to design a class to avoid leaking in such cases.Sub
@supercat: I wasn't sure, so I tested it out. Looks like the C++/CLI compiler wraps the child constructor in a try-fault block, and calls the parent's dispose method. However, if you're writing both the parent & child classes, you could do the same thing, catch exceptions in the child, call the parent's dispose, and then re-throw the exception. See edit.Lockridge
Thanks for the information. That seems a pretty clear advantage to C++/CLI. Unfortunately, I suspect such protection only works if the child class is written in C++/CLI, and not if a class written in C++/CLI is inherited by a class in some other language. To my mind, if a class has an IDisposable field which will never be written after construction is complete, it's much cleaner to have one line take care of declaration, initialization, and cleanup, than to have such things taken care of in three separate parts of the code. In vb.net it's possible for field initializers to use...Sub
...the values of constructor parameters without using thread-static variables (have a derived-class constructor pass them to a base-class constructor, which can then store them in a base-class field which the derived-class field initializers can use). If the code which calls the constructor passes in something like a List<IDisposable>, field-initializer expressions can use that to build a list of things that will need cleaning up. The code which calls the constructor would have to take care of the items in the list if construction fails, but it would have the information needed.Sub
I find it ironic that C# has special handling for finalize (which could be handled just fine without such features if code was simply allowed to override Object.Finalize()), but makes it difficult for objects to use lifetime-bound IDisposable objects without risk of leakage.Sub
S
1

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.

Snowball answered 21/8, 2012 at 0:50 Comment(2)
"You never specify the interface name." But this line is in the source code: public ref class ClassName: public System::IDisposableSwenson
Just writing a destructor in C++/CLI is enough to force the interface to be implemented. Just try it with the snippet I posted.Snowball

© 2022 - 2024 — McMap. All rights reserved.