To wrap this up, I try to explain how variables and references work as best as I can. I understand that there are some programming languages out there (especially interpreted scripting languages) which behave in a really odd way which may teach you some strange ideas about how computers work. One such languages which I use at work on a daily basis is PHP which has some really messy behaviour when it comes to instances and when shallow copies happen. Even things like arrays when passed around can be copied automatically which is such a weird concept.
C#, even though it uses a quite different memory management as languages such as C or C++, behaves actually quite similar to C and most other languages. A “reference” is not really the same as a “pointer” but from a pure functional point of view it behaves almost the same and is a good mental image when you try to reason about it.
Class instances
A class instance is never created automatically in a hidden way. Class instances need to be created with “new” (*1) which would allocate a chunk of memory on the heap where the class instance is initialized. As I said, references are not really pointers but you can think of them like that. So just imagine that a reference is actually the memory address where the instance is located in memory. You can use this address to “reach out” to the instance and interact with it. The instance is never “moved” as it is located at this specific address.
(*1)
I said all class instances have to be created with “new” and that is true without exception. However, especially in Unity we have seemingly some “exceptions” to that rule, especially when we talk about Components and MonoBehaviours. However that’s not actually true. Even Component instances have to be created with “new”.Though since Components have a native C++ counterpart in the Unity core, the creation of the managed C# instance is handlled by Unity internally. So Component instances can only be created through the AddComponent method. It will actually create the native and managed instances and link them together internally. Also Components can’t live on their own as they always have to be part of a GameObject. That’s another reason why you can’t / shouldn’t create Components manually with new.
Technically you can create a MonoBehaviour class manually with “new”, but you never should because this instance would be a “dead” instance as it’s missing its native counterpart. So never actually do that.
Address / reference
An address is the equivalent of a real world address. Unlike a real world address that may consist of a country, city, zip code, street and house number, addresses in a computer are just memory addresses. On 32 bit systems an address has 32 bits, so it’s just a normal integer value. On 64 bit systems it’s a 64 bit integer (long) value. Managed references work a bit differently under the hood, but the concept is the same.
Variables
A variable is just a certain memory space that has been given a name / alias. To which memory location a variable name refers to depends on the scope of the variable. Variables can be located as part of an object (instance variable), as part of a class (static variable) or could be “allocated” on the stack (local variable). In any case a variable is always just a chunk of memory that holds “data”.
Variables of primitive types such as int, bool, float, char and even “structs” directly contain the value you store in that variable inside that memory region that the variable refers to. That’s why those types are called “value-types” as the variable memory directly contains the value you store in that variable. Variables of reference types do not contain the object itself but just the address / reference to that instance. That’s why they are called reference-types. So the “value” that is stored in a reference type variable is just the reference / address to an instance, or “null” (which is literally just the value zero 0
)
Assignment / passing of variables
When you assign “something” to a variable, you store a value in that variable that matches its type (since C# is a type safe language). For value-types that means when you assign value to a variable, you simply copy that value into the variables memory. For reference type variables, when you assign an object or another variable to such a variable, you also just store the address / reference in that variable since that’s what is actually stored in that variable. The same thing happens when you pass variable as parameters to methods. The parameters of a method are simply local variables that temporarily live on the stack and values of the passed variables are copied into the local variables of the method. This is true for value type and reference type variables. That does not mean that an object is somehow duplicated. Remember that the content of a reference type variable is just the address of the object. So you will copy the address into the local variable. The method can use that address to reach out to the place on the heap where the actual object is stored and interact with that object.
So if you have two reference type variables a
and b
each of those variable has some memory area that holds the current reference / address of that variable. When you do
a = b;
it means you just copy the address that is currently stored in b into a. This has absolutely no effect on the object that might live behind that address. Note that the address could even be “null”, so it might not even be any object instance involved. Of course whatever was stored in a is simply overwritten. When “a” was referencing another instance, this would no longer be the case. This has no immediate effect on the instance that was referenced by “a”. However when the garbage collector scans through all references and no other variable would hold a reference to that instance it would notice that and the instance would be garbage collected.
Back to your code
In the code of your post #6 you had 3 variables: a, b and c. You also create exactly two instances of your class “myClass”. So you only have those two instances. The address of Instance1 is first stored in variable “a”. Then you copy that address into variable “b”. So now “a” and “b” both contain the same address / reference to instance1. After that you create instance2 and you store the address of that instance in variable “c”. In the end you again copy the address stored in “c” (which is a reference to instance2) to variable “b”. So after that variable “a” still references instance1 and variable “b” and “c” both reference instance2.
Final word about the ref and out keywords
You may come across methods which have parameters which have the ref
or out
modifier attached. Those parameters are treated differently. The local variables of those parameters are not “normal” variables but are actual pointers to the memory of the variable that was passed to the method. They act like the famous “double pointers” or “pointers or pointers” when we talk about reference types. How that is actually implemented is irrelevant. What you have to understand is that the local variable name essentially becomes a direct “alias” of the variable that you passed in. So anything you do with the local variable inside the method you actually do to the original variable. So when you store a value in that variable, you actually modify the content / value / address of the original. Though ref and out parameters requires that you use the ref keyword when calling the method. So you can not accidentally pass a variable to a method that could change your variable.
Many programmers mistake “reference types” as being “passed by reference” which is not true. Only “ref” parameters are actually “passed by reference”. They simply take the fact you can use a reference to reach out to the referenced object as being passed by reference. However passing by reference or passing by value is only concerned with the “variable”, not with its content. By default ALL arguments are passed by value. For reference types that means the address is copied into the local variable.
ps: I just noticed that @halley1 has the same idea of writing a “verbose” answer ^^. Hopefully this clears up some confusion for future readers.