-2

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);
}
Peter O Brien
  • 105
  • 2
  • 6
  • 1
    filldictmethod always destoys its callers dicionary and loads it from _context.owner, so all the fancy dicta or dictb stuff gets overwritten – pm100 Dec 25 '22 at 23:33
  • @pm100 localDict is populated after the response from FillDictMethod, but the static dictA / dictB root dictionary is not. If the dictRequester(passed by ref) was destroyed, then the localDict in DictControllerMethod should be empty. – Peter O Brien Dec 25 '22 at 23:48
  • 2
    Side note: naming is hard. You should not name the method that creates a new dictionary as "Fill" - this is confusing as it *sounds* like the method is adding elements to the existing dictionary, while code explicitly notes that it does not do that "its generating a new Dict". – Alexei Levenkov Dec 26 '22 at 00:16
  • You're assigning a new reference (address) in `FillDictMethod`. Your reference will no longer point to `dictA`. If you want it to work as expected, fill the current reference with the results. – WBuck Dec 26 '22 at 03:45

2 Answers2

0

You're assigning a new reference (address) in FillDictMethod. Your reference will no longer point to dictA.

If you want it to work as expected you'll need to mutate the ref variable and not just reassign it to point somewhere else.

static void FillDictMethod( ref Dictionary<string, string> dictRequester )
{
    // Add the values to the dictionary pointed to
    // by dictRequester.
    dictRequester.TryAdd( "Test", "Test" );
}
WBuck
  • 5,162
  • 2
  • 25
  • 36
  • Thanks @WBuck, in my real world scenario the dictionary is populated by a DbContext, therefore it will result in a new reference-type dictionary. I think i've identified the optimal way of handling this within my answer - but i'm open to improvements. Whats happening "under the hood" with types in C# can be sometimes be allusive. – Peter O Brien Dec 26 '22 at 16:54
  • @PeterOBrien same thing would happen in `C++` for the record. – WBuck Dec 26 '22 at 22:12
0

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);
}
Peter O Brien
  • 105
  • 2
  • 6