Search code examples
c#dictionarypass-by-referencerefreference-type

Unexpected behavior using By Ref with Reference type Dictionary


Experienced an unexpected behavior when using Dictionary type and passing by reference. Within a nested call, the reference to the underlaying base dictionary seems to be lost or replaced. All child method calls are passing the dictionary by ref.

public void Main() {
   // Get Dictionary and fill if empty - Type 1 Dictionary
   Dictionary<string, string> dictStored = DictionaryFetch(1);
   
   // Pull from previously filled and stored static dictionary
   dictStored = DictionaryFetch(1);
}

My understanding is that I am passing the address reference of the local type (which itself is a reference type object - Dictionary).
If a dictionary is assigned on the child method that action occurs on the parent dictionary (meaning its the same object, same memory address ref).

Within DictionaryFetch(), if the dictionary is empty and needs to be created, the final switch..case assignment to the static dictionary should not be required. I would like to remove this final reassignment to the root static dictionary.

// Method to find and return the correct Dictionary by Type.
void DictionaryFetch(int DictType)
{
    
  Dictionary<string, string> localDict = new();
    
  // Retrieve Dict pass by reference  
  // localDict will contain static dictA or dictB contents
  DictionaryFetchStatic(dictType, ref localDict);

  // Check dictionary, create if empty
  if (localDict.Count == 0)
  {
     // Method to populate localDict with entries.
     CreateDictionary(ref localDict);  
         
     // Here is the gotcha, the static dictA or dictB is still empty, 
     // but localDict is populated - hence the need for the following switch statement
     switch(dictType)
     {             
        case 1:                
             dictA = localDict;
             break;                
        case 2:                
             dictB = localDict;
             break;
     };
   }
   return localDict;
}

What am I missing? Why is the dictA not populated before the final switch..case statement in DictionaryFetch()?

static Dictionary<string, string> dictA = new();
static Dictionary<string, string> dictB = new();

void DictionaryFetchStatic(int dictType, ref Dictionary<string, string> dictRequester)
{
    switch(dictType)
    {             
        case 1:                
            dictRequester = dictA;
            break;                
        case 2:                
            dictRequester = dictB;
            break;
    };
}

void CreateDictionary(ref Dictionary<string, string> dictRequester)
{
    // Just an example of creating a dictionary using DbContext, its generating a new Dict in response.
    dictRequester = _context.owner.Where(x => x.type == 'abc')
           .ToDictionary(o => o.key, o => o.value);
}

Solution

  • Thanks for helpful response tips. In my real-world scenario the fill method will use a DbContext and will generate a new very large dictionary.

    The solution is to avoid use of local scope defined dictionary var, instead define a ref dictionary within DictControllerMethod(). This avoids the [pointer to a pointer] issue as reported in my question - just using and passing a single pointer to the root dictionary.

    Its use of local defined dictionary vars that cause the unnecessary additional layers. The use of a reference-type[dictionary] by value added to the confusion.

    public void Main() {
    
       int dictionaryType = 1; 
    
       // Get Dictionary and fill if empty - Type 1 Dictionary
       Dictionary<string, string> dictStored = DictionaryFetch(dictionaryType);
       
    
       // Pull from previously filled and stored static dictionary
       dictStored = DictionaryFetch(dictionaryType);
    }
    

    Methods:

    static Dictionary<string, string> dictA = new();
    static Dictionary<string, string> dictB = new();
    
    ref Dictionary<string, string>  DictionaryFetchStatic(int dictionaryType)
    {
        switch(dictionaryType)
        {             
            case 1:                
                return dictA;
            case 2:                
                return dictB;            
        };
    }
    
    // Method to find and return the correct Dictionary by Type.
    Dictionary<string, string> DictionaryFetch(int dictionaryType)
    {   
      // localDict will point to static dictA or dictB Dictionary reference type.
      ref Dictionary<string, string> localDict = ref DictionaryFetchStatic(dictionaryType);
    
      // Check dict, create if empty
      if (localDict.Count == 0)
      {
         // Method to fill localDict with entries.
         CreateDictionary(ref localDict);           
       
       }
       return localDict;
    }
    
    void CreateDictionary(ref Dictionary<string, string> dictRequester)
    {
        // Just an example of filling a dictionary using DbContext, its generating a new Dict in response.
        dictRequester = _context.owner.Where(x => x.type == 'abc')
               .ToDictionary(o => o.key, o => o.value);
    }