Reputation: 5459
I have a case where I don't understand why C# behaves like this.
The code is as follows:
private void Add(Dictionary<string,string> dct)
{
dct.Add("ADD", "ADD");
}
private bool Do(Dictionary<string, string> dict)
{
AddDict(out dict);
Add(dict);
return true;
}
private bool AddDict(out Dictionary<string, string> dct)
{
dct = new Dictionary<string, string>();
dct.Add("AddDict", "AddDict");
return true;
}
private void Form2_Load(object sender, EventArgs e)
{
var dct = new Dictionary<string, string>();
Do(dct); # dct is empty
Add(dct); # dct contains {[ADD, ADD]}
AddDict(out dct); # dct contains {[AddDict, AddDict]}
}
I don't understand why in the first case dct
remains empty although in the function Do
the variable dict
contains value and as dct
is a reference type I would expect that it should be non empty after Do
has returned.
Upvotes: 0
Views: 1013
Reputation: 43929
var dct = new Dictionary<string, string>();
var dict=dct;
now the both variables reference the same empty dictionary.
dict = new Dictionary<string, string> { { "ADD", "ADD" }};
now dict inside of Do references a new dictionary with new records, but dct outside of Do still references the previous empty dictionary.
To fix it, both variables should reference the same peace of memory. You can do it using ref. Now everything is working as expected
Do(ref dct);
private bool Do(ref Dictionary<string, string> dict)
{
AddDict(out dict);
Add(dict);
return true;
}
Upvotes: 2
Reputation: 220
Maybe I can help with some diagrams to better explain the behaviour.
Before doing that, it is important to understand that when we have a method expecting a reference type as one of its paramters, a copy of the pointer will be passed in when we call the method with an argument. Whenever the paramter is marked either with ref
or out
, that it is not the case anymore. Instead, a pointer towards the pointer of the argument is passed in.
Before moving on, we know that objects are always stored in the heap. Value types and pointers are stored wherever they are declared. For example, arguments of method that are of value type are stored in the stack. The same principle applies to a pointer defined inside a method or a local variable. Let's consider a simple example, var person = new Person()
. In this case, the created object resulted from new Person()
will be stored in the heap. The named pointer towards that object, that is person
, will be stored in the stack and it will point to the newly created object that resides in the heap. Remember that this happens when we call a method. However, if we have an object that has fields, those will be stored in the heap alongside with the said object.
Now, moving on to the diagrams. When the code starts to execute, we create a new object (let's call it Dictionary 1) that is placed in the heap and a pointer towards it (that is placed on the stack) through the line var dct = new Dictionary<string, string>()
. Moving on, we call the Do()
method and the argument is saved on the stack. Remember that a reference type parameter copies the pointer of the argument. Thus, as it can be observed from the diagram below, we now have 2 pointers that point towards the same object on the heap. Moving further, the AddDict()
methods is called, but in this case the argument is passed in with the out
keyword. That means we now pass in a pointer towards the pointer. Again this is reflected on the diagram with the dct
pointing towards the dict
.
The dct = new Dictionary<string, string>()
inside the AddDict()
method is executed. This creates a new object on the heap and it makes the dct
to point to it. Since we have a "pointing chain", that makes dict
to point towards the new object. This is reflected in the diagram below. Then, the "AddDict", "AddDict"
key-value pair is added to the pointed dictionary.
Since the AddDict()
method has finished executing, all the resources related to it are discarded from the stack. Moving on, the Add()
method is called inside the `Do() method. The behaviour is reflected in the diagram below.
The Add()
method has finished executing so all its resources are disposed.
The same applies for the Do()
method when it's done executing. However, as you can observe from the diagram below, there is no pointer pointing towards Dictionary 2, the one to which we added the key-value pairs.
Now the Add()
method is called inside the Form2_Load()
and then its resources are disposed
The same applies for the AddDict()
method.
In the end we are left with this. As there are not pointers towards Dictionary 1 and Dictionary 2, they will be collected by the Garbage Collector.
I really hope it makes sense and I shone some light regarding the behaviour that takes place behind the curtains.
Upvotes: 1