Set a reference field to null when the object is no longer needed.

Setting reference fields to null may improve memory usage because the object involved will be unreferenced from that point on, allowing the Garbage Collector to clean-up the object much earlier.

Note: This recommendation should not be followed for a variable that is about to go out of scope.

Avoid implementing a destructor.

The use of destructors in C# is demoted since it introduces a severe performance penalty due to way the Garbage Collector works. It is also a bad design pattern to clean up any resources in the destructor since you cannot predict at which time the destructor is called.

Use the using{} If the object implements IDisposable.

The common language runtime’s garbage collector reclaims the memory used by managed objects. Types that use unmanaged resources should implement the IDisposable interface to allow the memory allocated to these resources to be reclaimed. The using statement in C# simplifies the code that you must write to create and clean up an object. The using statement obtains one or more resources, executes the statements that you specify, and automatically disposes of the object. However, the using statement is useful only for objects that are used within the scope of the method in which they are constructed.

Ref: Microsoft Docs, Apr 2019, Using objects that implement IDisposable, https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/using-objects

If a destructor is needed, also use GC.SuppressFinalize.

If a destructor is needed to verify that a user has called certain cleanup methods such as Close()on a object, call GC.SuppressFinalize in the Close() method. This ensures that the destructor is ignored if the user is properly using the class. The following snippet illustrates this pattern:

public class Message
{
  bool connected = false;
  public void Connect()
  {
    // Do some work and then change the state of this object.
    connected = true;
  }
  public void Close()
  {
    // Close the connection, change the state, and instruct the GC
    // not to call the destructor.
    connected = false;
    GC.SuppressFinalize(this);
  }

  ~Message()
  {
    // If the destructor is called, then Close() was not called.
    if (connected)
    {
    // Warning! User has not called Close(). Notice that you can't
    // call Close() from here because the objects involved may
    // have already been garbage collected.
    }
  }
}

Implement IDisposable if a class uses unmanaged or expensive resources.

If a class uses unmanaged resources such as objects returned by C/C++ DLLs, or expensive resources that must be disposed of as soon as possible, you must implement the IDisposable interface to allow class users to explicitly release such resources.

Do not access any reference type members in the destructor.

When the destructor is called by the GC, it is very possible that some or all of the objects referenced by class members are already garbage collected, so dereferencing those objects may cause exceptions to be thrown.

Only value type members can be accessed (since they live on the stack).

Always document when a member returns a copy of a reference type or array

By default, all members that need to return an internal object or an array of objects will return a reference to that object or array. In some cases, it is safer to return a copy of an object or an array of objects. In such case, always clearly document this in the specification.