Background
If you're new to C# which I'm assuming you are, following this series. Let's look at what this actually means, but first we need to understand how C# code works under the hood.
To understand this, we first must dive into memory management used across multiple OOP languages, the Heap and Stack.
What do these words mean?
The Stack is the part of memory which in essence keeps track of everything that is executed (ran) in our code, and value types.
Whereas the Heap is responsible for keep track of our objects and structures (reference types).
In C# there are variables of Value type and Reference types.
Value types are:
- bool
- byte
- char
- decimal
- double
- enum
- float
- int
- long
- sbyte
- short
- struct
- uint
- ulong
- ushort
Reference types are:
- class
- interface
- delegate
- object
- string
There is another thing involved in this process called Pointers. A Pointer is an address for the location in memory for the reference type it belongs to.
A Pointer (or Reference) and a Reference Type are distinct from one another in that a Pointer is used to retrieve a Reference Type. A pointer is a portion of memory that directs attention to another portion of memory.
Think of it as you have created a Reference type variable
Building house = new Building();
Your house, and a Pointer is your address. The pointer is allocated to the Stack (is your address), and the house reference type is allocated to the Heap. People know how to get to the house, via the Pointer (address).
The Stack:
Think of it like those child toys you used to play with in doctors surgeries, placing blocks on each spike, but you can't access the bottom block without taking the top ones off first.
The Stack works on a Last In First Out pattern. When a method is called, a new stack frame is created and pushed onto the stack. The stack frame contains space for the method's parameters, return address, local variables, and other relevant information.
As the method executes, its local variables and parameters are stored within its stack frame.
When we're done with the top box (the method is done executing) we pop
it off the stack and proceed to use the data in the previous box on the top of the stack.
The Heap:
The Heap takes care of storing the Reference type variables.
The heap is more like that pile of shoes you leave at your front door for when you need to grab a pair of shoes but any will do. It's accessible at any time using your pointer because you know exactly where to look.
Golden Rules:
A Reference Type always goes on the Heap
Value Types always go on the Stack - Unless, they're declared within a Reference Type, then they go on the Heap.
Pointers to the reference type are stored on the Stack.
Global variables will be stored on the Heap as they need to be available to everything.
The Stack, as we mentioned earlier, is responsible for keeping track of where each thread is during the execution of our code (or what's been executed).
When the code executes a method, the main thread starts executing the instructions, and the code instructions live in the method tables. However, it also puts the method's parameters on the stack too. As the code runs, the variables within the methods are also pushed onto the stack.
Let's see this in an example, it might be easier:
Example 1:
public int Multiply(int value1, int value2)
{
int result; // Stack
result = value1 * value2;
return result;
}
We call the Multiply
method, and as soon as we do, the method's parameters are pushed onto the stack (the method itself does not live on the stack). So currently stack would look something like this:
value2 | int
value1 | int
The thread executes the code's instructions, and as it runs through the code, it sees the result
variable. As we know local Value type variables are pushed onto the stack.
result | int
value2 | int
value1 | int
The method finishes executing and returns the result
variable, thus popping it off the stack.
All memory allocated on the stack is cleaned up.
In this example, our result
variable is placed on the stack. As a matter of fact, every time a Value Type is declared within the body of a method or parameters it will end up on the Stack.
Example 2:
public void Main(string[] args){
private int number = 0;
Application app = new Application();
}
public class Application {
private int number = 42;
}
Application Class = Reference Type
Ok, so looking at the example above where do you think the variables will be allocated to ?
Let's di-sect:
number
is a local Value type variable within our program meaning it will be allocated to the Stack.
The app
variable of Reference type , so it will be be allocated to the Heap and a Pointer will be allocated to the Stack to get to it. Stack.
Here's a tricky one, where will the private int number
be assigned to ?
The Heap too ! As its a top level property within a Reference Type.
Let's add something a little more complex, to get the cogs working.
What about the below example:
csharp
public void Main(string[] args){
private int number = 0;
Application app = new Application();
app.SomeMethod(420);
}
public class Application {
private int number = 42;
public void SomeMethod(int number){
}
}
Ok, so where would we expect the number
argument to be assigned when the SomeMethod
method is called?
Well this is where people often get confused. Yes it is inside a Reference type, however it is a method parameter so therefore goes on the Stack! As we know all method parameters of Value type and local method variables are assigned to the Stack.
Important Note:
When using Reference types, the actual variables are referring to the Pointer to the type, not each object itself.
Wait, what do I mean ? Let's take a look:
Example:
public class Application
{
public int NumberOfThreads { get; set; }
}
public void Main (string[] args){
Application app1 = new Application();
Application app2 = new Application();
app1.NumberOfThread = 10;
Console.WriteLine(app1.NumberOfThreads) // 10
app2 = app1;
app2.NumberOfThreads = 40;
Console.WriteLine(app1.NumberOfThreads); //40
}
What ? Why has app1
Number of Threads changed, we haven't updated it.
Well when we assigned app2 to app1, it does this by Reference. It adds a pointer to the same reference type on the heap as the 1st pointer.
Therefore, when we updated the value of NumberOfThreads
on app2
, we're updating the now shared reference, meaning app1
and app2
values are the same value, see image below to try and help:
It can be a tough concept to grasp, but hopefully it makes sense, as it will lead on nicely to my next topic "By ref vs By value" method parameters.
As always, leave a comment if you would like anything else explained, and don't forget to subscribe, or give me a follow on twitter for updates on new content. :+