如何更新从 TryGetValue 返回的引用后将其插入集合中?

How can I update a reference returned from TryGetValue after its been inserted into a collection?

提问人:Tea 提问时间:11/17/2022 最后编辑:Tea 更新时间:11/17/2022 访问量:76

问:

我在字典里有一本字典。我想将对内部字典的引用设置为一个值,将其添加到外部字典中,如下所示:

var mammalIdSubscribers = new Dictionary<int, Dictionary<Guid, int>>();
        
var mammalId = 0;
if (!mammalIdSubscribers.TryGetValue(mammalId, out var mammalSubscribers))
    mammalIdSubscribers[mammalId] = mammalSubscribers; // Add reference to inner dict to outer dict
        
Subscribe(ref mammalSubscribers);

/* 
mammalIdSubscribers[mammalId] is still null after the call 
to Subscribe despite mammalSubscribers being non-null. Why?
*/
        
static void Subscribe(ref Dictionary<Guid, int> subscribers)
{
    subscribers = new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
}

不幸的是,这不起作用,我不确定为什么(抛出空引用异常)。 Console.WriteLine(mammalSubscribers.First().Value);

有人可以解释为什么这不起作用吗?换句话说,为什么仍然在调用之后使用关键字?mammalIdSubscribers[0]nullSubscriberef

C# 字典 参考 ref out

评论

0赞 Dai 11/17/2022
Dictionary不会公开其内容以供引用访问,即您无法进入其内部哈希表桶之一。您只能对公开属性的类型执行此操作,并且必须使用: learn.microsoft.com/en-us/dotnet/csharp/language-reference/... - 另请参阅运算符的文档:learn.microsoft.com/en-us/dotnet/csharp/language-reference/... refpublic ref...ref local= ref
0赞 Poul Bak 11/17/2022
你在哪里使用这个方法?你确定在那里定义吗?Console.LogmammalSubscribers

答:

1赞 Dai 11/17/2022 #1

您的变量和 的名称非常相似,因此为了清楚起见,我将重命名为“”或“biggerDict”,同时重命名为“encarta”,因为我经常将其用作参考mammalIdSubscribersmammalSubscribersmammalIdSubscribersouterDictmammalSubscribers

逐行...

  1. var biggerDict = new Dictionary<int, Dictionary<Guid, int>>();
    • 这给了我们一个有效但空洞的字典。biggerDict
  2. var mammalId = 0;
    • 明显。
  3. biggerDict.TryGetValue(mammalId, out var encarta)
    • 其计算结果为 。参数也是一个内联声明,当您使用带有 's 的内联声明时,新变量在返回时将是 (或 )。falseoutoutoutDictionaryTryGetValuenulldefaultfalse
    • ...它将返回,因为如前所述是空的。falsebiggerDict
    • ...因此是 .encartanull
    • (如果你眨了眨眼,错过了它:encarta 是堆栈上一个新的 GC 引用类型变量,它不是 biggerDict 任何部分的别名或“引用”)。
  4. 因为调用在语句中,所以意味着将对其进行计算。TryGetValueif( !TryGetValue(...) )biggerDict[mammalId] = encarta;
    • 并且仍然是.encartanull
    • ...因此(又名)是 。biggerDict[mammalId]biggerDict[0]null
  • Subscribe(ref encarta);
    • 这会将对局部变量的引用传递给 。encartaSubscribe
    • 至关重要的是,它不是对任何插槽或空间的引用:它仍然只是一个堆栈分配的(又名自动的)对象引用大小的插槽。encartabiggerDictnull
  1. encarta = new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
    • 在内部,在机器语言级别,指向堆栈分配的本地的指针 (-ish) 被尊重并分配给该 。Subscribeencartanew Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
    • ...这意味着现在不是 nullencarta
    • 然后执行返回到上一个函数。
  2. local 现在是对 GC 堆上该有效字典对象的引用。但是从来没有调用过 biggerDict[int].set_Item 属性 setter 来使 biggerDict[0] 成为对 encarta 指向的同一对象的非 null 引用encarta
  3. 请记住,除了数组 () 之外,所有其他带有索引器的类型都只是对属性 getter/setter 方法的糖,这意味着对象引用是通过值传递的,而不是 references-passed-by-reference 传递的 - 至少没有 -return 属性,这是不行的。T[]refDictionary<K,V>