Proper Disposal of Unmanaged Resources
When working with unmanaged resources such as file handles, database connections, or network sockets, explicit disposal becomes critical. The using statement provides automatic cleanup when the block exits, whether normally or due to an exception:
public void ProcessFileContents(string path)
{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
Console.WriteLine(Encoding.UTF8.GetString(buffer));
}
}
Implementing the Dispose Pattern
Classes that hold managed or unamnaged resources should implement IDisposable to enable deterministic cleanup. The following pattern ensures resources are released precisely when needed:
public class ResourceHolder : IDisposable
{
private bool _isDisposed = false;
private IntPtr _unmanagedHandle;
private Stream _managedStream;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
_managedStream?.Dispose();
}
if (_unmanagedHandle != IntPtr.Zero)
{
NativeMethods.ReleaseHandle(_unmanagedHandle);
_unmanagedHandle = IntPtr.Zero;
}
_isDisposed = true;
}
}
~ResourceHolder()
{
Dispose(false);
}
}
Event Handler Memory Leaks
Failing to unsubscribe from events creates strong references that prevent garbage collection. Always pair subscription with unsubscription:
public class EventSource : EventArgs
{
public event EventHandler<DataChangedEventArgs> DataChanged;
}
public class EventListener
{
private readonly EventSource _source;
public EventListener(EventSource source)
{
_source = source;
_source.DataChanged += OnDataChanged;
}
private void OnDataChanged(object sender, DataChangedEventArgs e)
{
// Handle the event
}
public void Detach()
{
_source.DataChanged -= OnDataChanged;
}
}
Avoiding Circular References
Circular references between objects can delay garbage collection indefinitely. Design class relationships to allow proper cleanup:
public class Parent
{
private Child _child;
public void SetChild(Child child)
{
_child = child;
}
public void Clear()
{
_child = null;
}
}
public class Child
{
private WeakReference<Parent> _parent;
public void SetParent(Parent parent)
{
_parent = new WeakReference<Parent>(parent);
}
}
Using WeakReference breaks the strong reference cycle and allows both objects to be collected when no other references exist.
Singleton Considerations
Static fields persist for the application lifetime. Singleton implementations that hold resources should Lazy-load and provide cleanup mechanisms:
public sealed class ServiceLocator
{
private static Lazy<ServiceLocator> _instance = new Lazy<ServiceLocator>(() => new ServiceLocator());
private ServiceContainer _container;
public static ServiceLocator Instance => _instance.Value;
private ServiceLocator()
{
_container = new ServiceContainer();
}
public T GetService<T>() where T : class
{
return _container.Resolve<T>();
}
}
Value Types vs Reference Types
Value types stack-allocate and automatically clean up. Prefer structs for small, immutable data that doesn't require sharing:
public struct Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y)
{
X = x;
Y = y;
}
}
Referencce types suit scenarios requiring polymorphism, large data, or when the object must persist beyond its immediate scope.
Object Pooling
Frequent allocation and deallocation creates GC pressure. Object pooling reuse instances instead:
public class ObjectPool<T> where T : class
{
private readonly ConcurrentBag<T> _objects;
private readonly Func<T> _factory;
public ObjectPool(Func<T> factory)
{
_factory = factory;
_objects = new ConcurrentBag<T>();
}
public T Rent()
{
return _objects.TryTake(out var item) ? item : _factory();
}
public void Return(T item)
{
_objects.Add(item);
}
}
Reducing Boxing Operations
boxing converts value types to heap-allocated references, creating unnecessary allocations. Generic constraints and generic methods minimize these conversions:
public void ProcessValue<T>(T value) where T : struct
{
// No boxing occurs when T is a value type
}