Reputation: 179
I have a .NET Core C# class that wraps an unmanaged pointer and it should be freed on program exit along with other resource cleanup. However, the destructor is not being called. I have tried in both Debug and Release mode. I see that .NET Core apparently doesn't guarantee that destructors will be run, so what is a recommended workaround? IMO the main point of garbage collection is to avoid having the developer track references, so I find this behavior surprising, to say the least.
From MSDN: In .NET Framework applications (but not in .NET Core applications), finalizers are also called when the program exits.
public Demo {
IntPtr _ptr;
public Demo()
{
Console.WriteLine("Constructor");
_ptr = /* P-invoke external function */
~Demo
{
Console.WriteLine("Destructor");
/*P-invoke ptr deletion */
}
}
public static void Main()
{
Demo demo = new Demo();
demo = null;
GC.Collect();
}
Program output:
Constructor
<...>\Test.exe (process 7968) exited with code 0.
Upvotes: 5
Views: 2154
Reputation: 4824
More changes are needed to improve the likelihood that the finalizer will be called.
Btw, Finalizer is never guaranteed to be called. If you want to gurantee the resources release, implement IDisposable
and call Dispose()
before the app/method/code block exit. Additionally to make Dispose()
guaranteed to call (even if app crashes, except FailFast and StackOverflow) before exiting the code block, use try-finally
or using
statements.
Here's an example to play with.
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("[main] Constructing");
MyDisposable m = new MyDisposable(0);
MyMethod(1);
Console.WriteLine("[main] Disposing [object 0]");
m.Dispose();
Console.WriteLine("[main] GC Collecting");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("[main] Done");
Console.ReadKey();
}
private static void MyMethod(int i)
{
new MyDisposable(i);
}
}
public class MyDisposable : IDisposable
{
private int _id;
public MyDisposable(int id)
{
_id = id;
Console.WriteLine($"[object {_id}] Constructed");
}
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
Console.WriteLine($"[object {_id}] Disposing by Dispose()");
}
else
{
Console.WriteLine($"[object {_id}] Disposing by ~Finalizer");
}
Console.WriteLine($"[object {_id}] Disposed");
disposed = true;
}
else
Console.WriteLine($"[object {_id}] Already disposed!");
}
~MyDisposable()
{
Dispose(false);
}
}
Output
[main] Constructing
[object 0] Constructed
[object 1] Constructed
[main] Disposing [object 0]
[object 0] Disposing by Dispose()
[object 0] Disposed
[main] GC Collecting
[object 1] Disposing by ~Finalizer
[object 1] Disposed
[main] Done
Some read: Using objects that implement IDisposable.
Upvotes: 5
Reputation: 35037
The official Cleaning up unmanaged resources states:
If your types use unmanaged resources, you should do the following:
Implement the dispose pattern. (...)
In the event that a consumer of your type forgets to call Dispose, provide a way for your unmanaged resources to be released. There are two ways to do this:
- Use a safe handle to wrap your unmanaged resource. This is the recommended technique. Safe handles are derived from the System.Runtime.InteropServices.SafeHandle abstract class and include a robust Finalize method. When you use a safe handle, you simply implement the IDisposable interface and call your safe handle's Dispose method in your IDisposable.Dispose implementation. The safe handle's finalizer is called automatically by the garbage collector if its Dispose method is not called.
(...)
Implement a Dispose method contains:
Object.Finalize
'.Upvotes: 1