使用具有引用类型字典的 By Ref 时出现意外行为

Unexpected behavior using By Ref with Reference type Dictionary

提问人:Peter O Brien 提问时间:12/26/2022 最后编辑:Peter O Brien 更新时间:1/3/2023 访问量:83

问:

使用 Dictionary 类型并按引用传递时遇到意外行为。 在嵌套调用中,对底层基础字典的引用似乎丢失或被替换。 所有子方法调用都按 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);
}

我的理解是,我正在传递本地类型的地址引用(它本身就是一个引用类型对象 - Dictionary)。
如果在子方法上分配了字典,则该操作将在父字典上发生(这意味着它是相同的对象,相同的内存地址引用)。

内 ,如果字典为空,需要创建,则最后的开关。不应要求将大小写分配给静态字典。我想删除对根静态字典的最终重新分配。DictionaryFetch()

// 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;
}

我错过了什么?为什么在最终切换之前没有填充 dictA..?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);
}
C# 字典 传递 ref 引用类型

评论

1赞 pm100 12/26/2022
filldict方法总是对调用者进行 dicionary 并从 _context.owner 加载它,因此所有花哨的 dicta 或 dictb 内容都会被覆盖
0赞 Peter O Brien 12/26/2022
@pm100 localDict 在 FillDictMethod 的响应之后填充,但静态 dictA / dictB 根字典不是。如果 dictRequester(由 ref 传递)被销毁,则 DictControllerMethod 中的 localDict 应为空。
2赞 Alexei Levenkov 12/26/2022
旁注:命名很难。您不应该将创建新字典的方法命名为“Fill”——这令人困惑,因为听起来该方法正在向现有字典添加元素,而代码明确指出它不会这样做“它生成一个新的字典”。
0赞 WBuck 12/26/2022
您正在 中分配新的引用(地址)。您的引用将不再指向 。如果希望它按预期工作,请在当前引用中填充结果。FillDictMethoddictA

答:

0赞 WBuck 12/26/2022 #1

您正在 中分配新的引用(地址)。您的引用将不再指向 。FillDictMethoddictA

如果你想让它按预期工作,你需要改变变量,而不仅仅是将其重新赋值到其他地方。ref

static void FillDictMethod( ref Dictionary<string, string> dictRequester )
{
    // Add the values to the dictionary pointed to
    // by dictRequester.
    dictRequester.TryAdd( "Test", "Test" );
}

评论

0赞 Peter O Brien 12/27/2022
感谢@WBuck,在我的实际场景中,字典由 DbContext 填充,因此它将产生一个新的引用类型字典。我想我已经在我的答案中确定了处理这个问题的最佳方法——但我对改进持开放态度。C# 中类型的“幕后”发生的事情有时可能是暗示性的。
0赞 WBuck 12/27/2022
@PeterOBrien同样的事情也会发生。C++
0赞 Peter O Brien 12/27/2022 #2

感谢您提供有用的回复提示。 在我的实际场景中,fill 方法将使用 DbContext 并生成一个新的非常大的字典。

解决方案是避免使用本地范围定义的字典 var,而是在 DictControllerMethod() 中定义一个 ref 字典。这避免了我的问题中报告的 [指针指向指针] 问题 - 仅使用并传递指向根字典的单个指针。

它使用本地定义的字典变量,这会导致不必要的附加层。使用 reference-type[dictionary] by value 增加了混淆。

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

方法:

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