我有一个字典里面的字典。在添加到外部字典之后,我想将对内部字典的引用设置为值:
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);
抛出空引用异常)。
有人能解释一下为什么这不起作用吗?换句话说,为什么用ref
关键字调用Subscribe
后,mammalIdSubscribers[0]
仍然是null
?
您的变量mammalIdSubscribers
和mammalSubscribers
的命名非常相似,因此为了清晰起见,我将mammalIdSubscribers
重命名为"outerDict
";或者是"biggerDict
";而mammalSubscribers
被重命名为"encarta
",因为我使用作为参考作为一个spprog。
逐行……
var biggerDict = new Dictionary<int, Dictionary<Guid, int>>();
- 这给了我们一个有效的,但是空的
biggerDict
字典。
- 这给了我们一个有效的,但是空的
var mammalId = 0;
- 不言自明。
biggerDict.TryGetValue(mammalId, out var encarta)
- 计算结果为
false
。out
参数也是一个内联out
声明,当你使用内联out
声明与Dictionary
的TryGetValue
,那么新的变量将是null
(或default
),当它返回false
。 - …并且它将返回
false
,因为biggerDict
是空的,如前所述。 - …因此
encarta
就是null
。 - (如果你眨眼错过了它:
encarta
是堆栈上的一个新的GC引用类型变量,它是而不是别名或"引用";到biggerDict
的任何部分)。
- 计算结果为
- 因为
TryGetValue
调用在if( !TryGetValue(...) )
语句中,这意味着biggerDict[mammalId] = encarta;
将被求值。- 和
encarta
仍然是null
- …因此
biggerDict[mammalId]
(又名biggerDict[0]
)是null
。
- 和
Subscribe(ref encarta);
- 将本地变量
encarta
的引用传递给Subscribe
。 - 至关重要的是,
encarta
为非对biggerDict
中的任何插槽或空间的引用:它仍然只是一个堆栈分配(aka自动)对象引用大小的插槽,仍然是null
。
- 将本地变量
encarta = new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
- 在
Subscribe
内部,在机器语言级别,一个指向堆栈分配的encarta
local的指针(-ish)被保留并分配给该new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
。 - …这意味着
encarta
现在是而不是null
。 - 执行返回到前一个函数。
- 在
encarta
local现在是对GC堆上有效的字典对象的引用。但是从来没有调用过biggerDict[int].set_Item
属性设置器来使biggerDict[0]
成为encarta
指向对象的非null
引用。。请记住,除了真正的数组(T[]
),所有其他具有索引器的类型都只是属性getter/setter方法的糖,这意味着对象引用是按值传递的,而不是按引用传递的——至少在没有ref
返回属性的情况下是这样,Dictionary<K,V>
没有这样做。