A memory leak transpires when a program fails to deallocate memory that it has previously allocated.
This oversight results in a progressive increase in memory consumption, which can ultimately degrade system performance and, in severe cases, precipitate application crashes.
Let’s review some quickly written C# code that lacks proper memory management techniques, and then focus on improving it.
static void Main(string[] args)
{
var example = new MemoryLeakExample();
example.AddData();
var performance = new MeasurePerformance();
performance.PrintOutMemBytes();
}
public class MemoryLeakExample
{
private List<byte[]> dataList = new List<byte[]>();
public void AddData()
{
for (int i = 0; i < 1000; i++)
{
dataList.Add(new byte[1024]);
}
}
}
Imagine this code and similar methods in a critical application. The memory is never released, which could eventually cause the application to crash.
Let’s improve it. We’ll be utilizing the IDisposable interface which provides a mechanism for releasing resources. We’ll also be using a destructor which is a special method that is automatically invoked when an object is finalized (no longer reachable). Note: Relying solely on the destructor for cleanup is not recommended.
static void Main(string[] args)
{
using (var example = new MemoryLeakFixExample())
{
example.AddData();
}
var performance = new MeasurePerformance();
performance.PrintOutMemBytes();
}
public class MemoryLeakFixExample : IDisposable
{
private List<byte[]> dataList = new List<byte[]>();
private bool disposed = false;
public void AddData()
{
for (int i = 0; i < 1000; i++)
{
dataList.Add(new byte[1024]);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Free managed resources
dataList.Clear();
dataList = null;
}
disposed = true;
}
}
~MemoryLeakFixExample()
{
Dispose(false);
}
}
This solution implements the IDispable pattern correctly, ensuring that both managed and unmanaged resources are released properly. It optimizes garbage collection by preventing the finalizer from running if disposal has already occurred.