I have a dictionary inside a dictionary. I'd like to set a reference to the inner dictionary to a value after I'd added it to the outer dictionary as such:
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 } };
}
Unfortunately, this doesn't work and I'm not sure why ( Console.WriteLine(mammalSubscribers.First().Value);
throws a null reference exception).
Can someone please explain why this doesn't work? In other words, why is mammalIdSubscribers[0]
still null
after the call to Subscribe
with the ref
keyword?
Your variables, mammalIdSubscribers
and mammalSubscribers
, are very similarly named, so for the sake of clarity I'll rename mammalIdSubscribers
to "outerDict
" or maybe "biggerDict
" while mammalSubscribers
is renamed to "encarta
", because I used that as a reference a lot as a sprog.
Line-by-line...
var biggerDict = new Dictionary<int, Dictionary<Guid, int>>();
biggerDict
dict.var mammalId = 0;
biggerDict.TryGetValue(mammalId, out var encarta)
false
. The out
param is also an inline out
declaration, and when you use inline out
declarations with Dictionary
's TryGetValue
then the new variable will be null
(or default
) when it returns false
.false
because biggerDict
is empty, as established earlier.encarta
is null
.encarta
is a new GC reference-type variable on the stack, it is not an alias or "reference" to any part of biggerDict
).TryGetValue
call is inside an if( !TryGetValue(...) )
statement it means that biggerDict[mammalId] = encarta;
will be evaluated.
encarta
is still null
.biggerDict[mammalId]
(aka biggerDict[0]
) is null
.Subscribe(ref encarta);
encarta
to Subscribe
.encarta
is not a reference to any slot or space within biggerDict
: it's still just a stack-allocated (aka automatic) object-reference-sized slot that's still null
.encarta = new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
Subscribe
, at the machine-language level, a pointer(-ish) to the stack-allocated encarta
local is deferenced and assigned to that new Dictionary<Guid, int> { { Guid.NewGuid(), 10 } };
.encarta
is now not null
.encarta
local is now a reference to that valid dictionary object on the GC heap. But nothing ever invoked the biggerDict[int].set_Item
property setter to make biggerDict[0]
a non-null
reference to the same object that encarta
points to.T[]
), all other types with indexers are just sugar over property getter/setter methods, which means object references are passed by value, and not references-passed-by-reference - at least not without a ref
-returning property, which Dictionary<K,V>
does not do.