Code Pope
Code Pope

Reputation: 5459

C#: Dictionary remains empty although value is added in a function

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

Answers (2)

Serge
Serge

Reputation: 43929

  1. When you call Do(dct) , method creates a copy of dct that is called dict and dict reference the same memory heap that dct does. It is the same as
 var dct = new Dictionary<string, string>();
 var dict=dct;

now the both variables reference the same empty dictionary.

  1. When after this you call AddDict(out dict) inside of Do, AddDict creates another dictionary inside of the method and return reference to the new dictionary to dict. it is the same as
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

HSBogdan
HSBogdan

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.

enter image description here

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.

enter image description here

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.

enter image description here

The Add() method has finished executing so all its resources are disposed.

enter image description here

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.

enter image description here

Now the Add() method is called inside the Form2_Load() and then its resources are disposed

enter image description here

The same applies for the AddDict() method.

enter image description here

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.

enter image description here

I really hope it makes sense and I shone some light regarding the behaviour that takes place behind the curtains.

Upvotes: 1

Related Questions