使用ref传递引用类型Dictionary时出现异常行为
C#中Dictionary通过ref传递时引用丢失的问题分析
我在使用Dictionary类型并通过ref传递时遇到了异常行为——嵌套调用里底层字典的引用似乎丢失或被替换,所有子方法均通过ref传递字典。
public void Main() { // 获取字典,若为空则填充 - 类型1字典 Dictionary<string, string> dictStored = DictionaryFetch(1); // 从已填充并存储的静态字典中获取 dictStored = DictionaryFetch(1); }
我的预期是:传递的是本地引用类型(Dictionary)的地址引用,子方法中对字典的赋值应作用于父字典(即同一对象、同一内存地址)。在DictionaryFetch()方法中,如果字典为空且需要创建,理论上不需要最后对静态字典的switch..case赋值操作,我希望移除这部分代码,但目前做不到——因为执行switch语句前,静态的dictA仍然是空的,只有localDict被填充了。
// 根据类型查找并返回正确字典的方法。 Dictionary<string, string> DictionaryFetch(int DictType) { Dictionary<string, string> localDict = new(); // 通过引用检索字典 // localDict将包含静态dictA或dictB的内容 DictionaryFetchStatic(DictType, ref localDict); // 检查字典,若为空则创建 if (localDict.Count == 0) { // 填充localDict条目的方法。 CreateDictionary(ref localDict); // 问题点:静态dictA或dictB仍然为空,但localDict已被填充 - 因此需要以下switch语句 switch(DictType) { case 1: dictA = localDict; break; case 2: dictB = localDict; break; }; } return localDict; }
配套的静态字典及辅助方法代码:
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) { // 仅为使用DbContext创建字典的示例,会生成新的Dict。 dictRequester = _context.owner.Where(x => x.type == 'abc') .ToDictionary(o => o.key, o => o.value); }
问题根源分析
核心问题出在ref传递引用类型时的赋值逻辑:
- 第一步,
DictionaryFetchStatic通过ref将localDict的引用指向了静态的dictA(一个空的新字典实例)。 - 第二步调用
CreateDictionary(ref localDict)时,方法内直接给dictRequester赋值了一个全新的Dictionary实例(ToDictionary返回的新对象)。此时localDict的引用被替换为这个新实例,但静态的dictA还是原来那个空实例——因为ref传递的是localDict这个变量本身的地址,而非dictA的地址。你只是修改了localDict的指向,并没有改变dictA的引用。
简言之:ref传递允许你修改传入变量的引用,但如果该变量之前指向另一个对象(比如dictA),修改变量的引用不会影响原对象的引用。
解决方案
如果想移除对静态字典的switch重新赋值,有两种可行思路:
思路1:填充已有字典实例,不替换引用
修改CreateDictionary方法,不要直接赋值新实例,而是清空原有字典后添加内容:
void CreateDictionary(ref Dictionary<string, string> dictRequester) { // 清空原有内容 dictRequester.Clear(); // 查询获取新条目并填充到原有字典中 var newEntries = _context.owner.Where(x => x.type == 'abc') .ToDictionary(o => o.key, o => o.value); foreach (var entry in newEntries) { dictRequester.Add(entry.Key, entry.Value); } }
这样dictRequester始终指向原来的dictA实例,填充操作会直接修改dictA的内容,无需后续的switch赋值。
思路2:直接操作静态字典的引用
利用C# 7.0+的ref局部变量,直接关联静态字典的引用,赋值时直接修改静态字典的指向:
// 修改后的DictionaryFetch方法 Dictionary<string, string> DictionaryFetch(int DictType) { // 直接关联到静态字典的引用 ref var targetDict = ref DictType switch { 1 => ref dictA, 2 => ref dictB, _ => throw new ArgumentOutOfRangeException(nameof(DictType)) }; if (targetDict.Count == 0) { // 直接给静态字典赋值新实例,无需额外switch targetDict = _context.owner.Where(x => x.type == 'abc') .ToDictionary(o => o.key, o => o.value); } return targetDict; }
这种方式省略了中间变量localDict,直接操作静态字典的引用,赋值时会直接修改静态字典的指向,完全不需要后续的switch赋值逻辑。
内容的提问来源于stack exchange,提问作者Peter O Brien
相关产品推荐
相关产品推荐

