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:
~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?
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.
protected override void Finalize()
.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);
}
}
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()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With