简体   繁体   中英

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 = DictControllerMethod(1);
   
   // Pull from previously filled and stored static dictionary
   dictStored = DictControllerMethod(1);
}

My understanding is that i am passing an 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 DictControllerMethod() the final switch..case assignment of local dictionary to the static dictionary should not be required. In classic C++ the reference to root dictA/dictB object and localDict would be the same.

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

  // Check dict, fill if empty
  if (localDict.Count == 0)
  {
     // Method to fill localDict with entries.
     FillDictMethod(ref localDict);  
         
     // Here is the gotcha, the static dictA or dictB is still empty, 
     // localDict is populated,
     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 DictControllerMethod() ?

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

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

void FillDictMethod(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);
}

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" );
}

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.

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

ref Dictionary<string, string>  DictFetchStaticMethod()
{
    switch(dictType)
    {             
        case 1:                
            return dictA;
        case 2:                
            return dictB;            
    };
}

// Method to find and return the correct Dictionary by Type.
void DictControllerMethod(int DictType)
{   
  // localDict will point to static dictA or dictB Dictionary reference type.
  ref Dictionary<string, string> localDict = ref DictFetchStaticMethod();

  // Check dict, fill if empty
  if (localDict.Count == 0)
  {
     // Method to fill localDict with entries.
     FillDictMethod(ref localDict);  
         
     // Here is the gotcha, the static dictA or dictB is still empty, 
     // localDict is populated,
     switch(dictType)
     {             
        case 1:                
             dictA = localDict;
             break;                
        case 2:                
             dictB = localDict;
             break;
     };
   }
   return localDict;
}

void FillDictMethod(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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM