First, let's see the difference between delegate and delegate instance. A delegate (type) is a pure method signature; and a delegate instance is something different.
First surprise:
delegate instance type is completely different from the related delegate type.
delegate void Action();
Action myAction = () => { System.Console.WriteLine("action"); }
If you get
Action.GetType
and look at it at throw reflection, you will see, that
this is a… class with many members. Most importantly, it has a method
GetInvocationList
.
Delegate instance internally is not a pointer! Is is a collection of "pointers". But not pointers, its is a collection of other delegates with invocation lists each of one item. What is that item?
It is not a pointer either. Functionally, it's
"double pointer": one points to code of a handler, another is a
reference to class or structure instance passed to the handler during the call in the form of hidden parameter "this". This reference is null for static handlers. Both static and non-static (instance) handlers are allowed.
So,
any delegate instance is always multi-cast.
The process of adding handlers to invocation list is very interesting. Delegate instances loose their referential identity as a result of this operation, pretty much like strings, because these types are
immutable.
I describe this is my article
Dynamic Method Dispatcher[
^], please see the section "4.1 On the Nature of Delegate Instance".
Now, the following references to heap are involve: a references to the delegate instance itself, as an instance of class and the reference to each "this" of each handler in the invocation list. An pointer to the code does not matter and does not need garbage collection. Garbage Collector (GC) deals with those references exactly as with any other ones.
GC looks for all references which can not be accessed by any currently running code in the Application Domain. All inaccessible instances are scheduled for destruction, and the memory will be reclaimed, but some time later. (That's way any code relying on the order of destructor calls is incorrect and the destructors are rarely have to be written.) This is not trivial. If the some instances reference each other in a loop, they will be destroyed anyway.
If some access by references exists, it delays garbage collection. Having an instance of some class in some delegate's invocation list is not an exclusion. To make garbage collection possible, a delegate instance itself should be lost, or a handler should be removed from the invocation list.
By the way, failure to take it into account can make yet another source of memory leaks in a .NET application. Yes, memory leaks are possible, but they can happen due to design issues, not due to a random coding bug.
—SA