Understanding memory management is a key weapon in a developers arsenal. After developing in Java for many years, I progressed to C# which adds a few of its own nuances to memory management.
What goes on the Stack/Heap?On the stack - value types (int, float, struct, etc) + references to reference types. Typically this is the data structure which stores access to variables used in currently executing code.
The stack is split into "stack frames" - each containing all variables relating to the current executing method.
(LIFO data structure - Last In First Out)
The heap - contains the values of reference types. Does not keep track of execution state, instead is more of a data store.
Value types as referencesWhen new methods are called with value types as their parameters, those value types get copied into the stack frame of that new method. Obviously if you are doing this with large value types, you can run into potential problems (general performance, stack overflow, etc). To reduce the copying over head C# allows you to pass the value type as a reference instead.
For example, if we have:
struct testStruct{
float one, two, three, four, five, six, seven;
}
Each time we pass this struct to a new method as follows:
testStruct T = new testStruct();
exampleMethod(T);
The entire struct is copied on the stack as explained above, however, if we do the following:
exampleMethod(ref T);
We just pass a pointer/reference to the method, and only this pointer is copied on the stack. The side effect is that any changes made within the exampleMethod to the testStruct will also be reflected in the calling method. We are accessing the value itself as opposed to a copy of it.
Reference types by referenceNot only can we obtain references to value types in C# but it also allows us to obtain references to reference types! The same terminology is used as explained above with the "ref" keyword.
A reference reference (better name suggestions on a postcard!) not only gives us access to change the values of the original reference object, but also change the original pointer to that object. So we could point it to a different object or we could change the type of the pointer (from say String to Object - making the reference more general).
It is interesting to see that C# gives us this additional power on top of Java - I don't think it is necessarily something that will be required all the time, however, when it comes to performance tuning an application I see that these language additions will be of great benefit.
Deep copies
As with Java, C# also undertakes just shallow copies of objects (not copying any child objects) just references and primitives. In order to copy child objects too we need to implement the ICloneable interface in C# which includes the clone method e.g.
public class TestObject : ICloneable{
public string ParameterOne;
public object Clone(){
TestObject object = new TestObject();
object.ParameterOne = ParameterOne.Clone() as string;
return object;
}
}
I always wondered why this kind of method wasn't provided as standard within C# or Java, however, it appears that the concept of a true deep copy of any arbitrary type is not something easily (or even safely) achieved in a reference-based object-oriented language. While simple classes would be trivial to copy, reference loops, classes without parameterless constructors, and immutable types present challenges.