Though your view would also work as a mental image, conceptionally.
This may go too far, but just in case you don’t know, methods of a class (so the actual executable code) only exist once and all methods belong to the class and not really to an instance of the class. So called “instance methods” are often thought of being part of the object instance itself, but in reality the code for the method only exists once. Instance methods simply have an additional implicit first argument which is the object instance they are called on. That’s the this
reference inside a method.
A delegate simply holds the information of the method (MethodInfo) as well as the actual object instance. For static methods the object instance is null. For any other delegate, this instance reference is what is passed as first argument to the method.
A simple example
public class MyClass
{
public int myVar = 5;
public void Test(string arg)
{
Debug.Log("Test::" + arg + " myVar = " + myVar);
}
}
var obj1 = new MyClass();
obj1.Test("FooBar"); // prints "Test::FooBar myVar = 5"
This is just a small class with an instance method called Test. As you can see it takes a single string argument. The body of the method just logs this message to the console. Since it’s an instance method it has access to the myVar variable. As I said under the hood an instance method essentially has an implicit first argument and the method would actually look like this
public static void Test(MyClass this, string arg)
{
Debug.Log("Test::" + arg + " myVar = " + this.myVar);
}
The call of our Test method is essentially
MyClass.Test(obj1, "FooBar");
So multiple instances of MyClass really use the same method, but the method has the object instance reference as an additional parameter.
A delegate to a method stores two things, the (essentially static) method reference as well as a “target” reference to an object which is passed as the first “context” argument this
.
As I said a closure is a compiler generated class for your anonymous method. Anonymous just means it does not have an accessible name in any context of the language. Under the hood it actually has a name and has to be defined somewhere. Let me give you a very simplified example what happens when you create a closure and capture a local variable.
System.Action del;
void MyMethod()
{
int myLocalVar = 42;
del = () => Debug.Log("Value: " + myLocalVar++);
}
As you can see “myLocalVar” is a local variable that “usually” only exists inside the method and would usually be allocated on the stack. When the method is finished, the stack is cleaned up and the variable does not exist anymore. However when you create a closure like we did, the compiler generates a class that encapsulates that variable as well as the closure method we defined here. So the code would actually look more like this:
System.Action del;
private internal class MyMethodClosure
{
public int myLocalVar;
public void AnonMethod1()
{
Debug.Log("Value: " + myLocalVar++);
}
}
void MyMethod()
{
MyMethodClosure inst = new MyMethodClosure() { myLocalVar = 42 };
del = inst.AnonMethod1;
}
Note that this is very simplified pseudo code but it may help to understand what the compiler actually does for you. One important thing you should notice: When you call MyMethod, we create a new context / closure object that is used by the anonymous method we store in the delegate “del”. Invoking that delegate several times would first print 42, then 43, then 44 and so on. The method has captured that variable (which internally became a class member variable).
Calling MyMethod again would create a new closure instance and would overwrite the old one. So that old “inst” would be up for garbage collection since it is no longer referenced from anywhere.
The issue with for loops is, when you capture the for loop variable, the system would only create one context instance which would be used in each iteration. However if you declare a local variable inside the for loop body, it would be a new variable each iteration and you get a new context instance for each iteration. So each button gets its own delegate which use different context instances.
I’m sure I went way to far with this post, so I put it in a spoiler ^^. As I already mentioned in 99% of the cases you don’t really need to know how any of this works under the hood. You just need to be aware of the way how closures captures variables.