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