.NET Memory Management

The Garbage Collector (GC) takes care of memory management in .NET.

Well, it manages 'most' of the memory. There are scenarios when the GC cannot handle it. To understand this better in simple terms:

There are two main memory areas. One is Managed Memory, referring to the portion of memory managed by the .NET Common Language Runtime (CLR). It includes memory used for objects created by .NET languages like C#, VB.NET, F#. Memory allocation and deallocation for these managed objects are handled by the CLR's GC. The GC automatically manages the lifecycle of these objects, handling memory allocation and deallocation when objects are no longer referenced by the application.

Unmanaged memory refers to memory outside the control and management of the CLR. It includes memory allocated by native code, typically written in C/C++, which interacts directly with hardware and can manage memory, accessing system resources without the assistance of a higher-level runtime environment.

Whenever .NET apps interact with components outside the .NET runtime, like the file system, a database, or a REST API, they would require allocating memory outside the managed runtime environment to fulfill the usecase. This is often confusing because, as developers, we work with types in namespaces like System.Data and classes like SqlConnection, which are just .NET types. For example, here's what happens under the hood when a connection to the SQL server occurs. Although SqlConnection is a .NET type, the SqlConnection class in .NET serves as a managed wrapper around the lower-level native code responsible for communicating with the SQL Server database. Hence, the GC cannot deallocate the underlying unmanaged resource. This is when developers/consumers of these wrapper classes need to call the Dispose() method ('in most cases'), allowing the wrapper classes to free up unmanaged memory.

If you are dealing with legacy and large code bases, a good initial step can be enabling the static code analysis rule CA 2000, available as part of the default ruleset, to uncover issues.

If you use IDisposable objects as your class-level member fields, your class also needs to implement IDisposable and, in the implementation, call Dispose on the instance fields.