Efficient memory usage in .NET

2 min read
  • Minimize allocations. Be mindful of object allocations, especially in performance-sensitive code paths or loops, and reuse objects where feasible. For string manipulation, prefer StringBuilder over repeated string concatenation with +, to avoid creating many intermediate string objects.
  • Value types (struct) versus reference types (class). Use structs for small, data-centric types that don’t need reference semantics. Structs are value typesValue types and reference typesA value type variable holds the value. A reference type variable holds a reference to an object.Opens in a new tab, compared with classes in class versus structClass versus structA struct is a value type and a class is a reference type.Opens in a new tab. They can be allocated on the stack (for local variables) or inline within containing objects, which can reduce pressure on the GC heap. Be aware of the cost of boxing if structs are frequently converted to object or interface types.
  • Object pooling. For objects that are created and destroyed frequently, especially if they are expensive to initialize, consider pooling. That means reusing existing instances from a pool instead of creating new ones. ArrayPool<T> is a built-in example, for pooling arrays.
  • Span<T> and Memory<T>. These types let you work with contiguous regions of memory (from arrays, strings or unmanaged memory) without making copies or allocating new objects on the heap. They are very effective for high-performance scenarios like parsing and data manipulation, where you want to avoid allocations.
  • Unsubscribe from eventsEventsHow a class tells other classes that something happened, without knowing who is listening.Opens in a new tab. A common source of memory leaks is an object (the subscriber) subscribing to an event of another object (the publisher) and failing to unsubscribe when it’s no longer interested, or before it’s disposed. The publisher holds a strong reference to the subscriber, which prevents it from being garbage collected. Always make sure event handlers are unsubscribed, especially if the subscriber has a shorter lifetime than the publisher.
  • Weak references (WeakReference<T>). A weak reference lets the GC collect an object while still allowing the application to access it if it’s still alive. That can be useful for caching scenarios, or for breaking circular dependencies that might prevent garbage collection.
  • Profiling. Regularly use memory profiling tools (like those in Visual Studio, dotMemory, ANTS Memory Profiler and PerfView) to identify memory leaks, excessive allocations, LOH issues, and other memory-related performance bottlenecks.

Understanding the nuances of .NET memory management is critical, in particular the IDisposable patternIDisposable and the Dispose patternHow to release the resources the garbage collector does not know about.Opens in a new tab for unmanaged resources and the implications of event handlers for managed memory. The GC handles a lot automatically, but these areas need careful attention to prevent resource leaks and keep applications stable and fast.

The LOH also deserves special consideration for applications that handle large data objects. Strategies to minimize LOH allocations can significantly affect performance. See the GC noteGarbage collection and generationsThe runtime frees memory for you, sorted by how long objects live.Opens in a new tab for how it works.


Sources