https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/using-objects
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/unmanaged
For the majority of the objects that your app creates, you can rely on the .NET garbage collector to handle memory management. However, when you create objects that include unmanaged resources, you must explicitly release those resources when you finish using them.
The most common types of unmanaged resources are objects that wrap operating system resources, such as files, windows, network connections, or database connections.
Although the garbage collector is able to track the lifetime of an object that encapsulates an unmanaged resource, it doesn't know how to release and clean up the unmanaged resource.
Implementing the Dispose method is primarily for releasing unmanaged resources. When working with instance members that are IDisposable implementations, it's common to cascade Dispose calls.
There are additional reasons for implementing Dispose, for example, to free memory that was allocated, remove an item that was added to a collection, or signal the release of a lock that was acquired.
The .NET garbage collector does not allocate or release unmanaged memory. The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object.
The dispose pattern is used for objects that implement the IDisposable interface, and is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory. This is because the garbage collector is unable to reclaim unmanaged objects.
To help ensure that resources are always cleaned up appropriately, a Dispose method should be idempotent, such that it is callable multiple times without throwing an exception. Furthermore, subsequent invocations of Dispose should do nothing.