In my app I have a Dictionary<ContainerControl, int>
.
I need to check if a key is present in the dictionary and alter its corresponding value if key is found or add the key if not already present.
The key for my dictionary is a ControlContainer object.
I could use this method:
var dict = new Dictionary<ContainerControl, int>();
/*...*/
var c = GetControl();
if (dict.ContainsKey(c))
{
dict[c] = dict[c] 1;
}
else
{
dict.Add(c, 0);
}
but I think that this way if the key is already present, my dictionary is iterated three times: once in ContainsKey and twice in the if branch.
I wander if there is a more efficient way to do this, something like
var dict = new Dictionary<ContainerControl, int>();
/*...*/
var c = GetControl();
var kvp = dict.GetKeyValuePair(c); /* there is no such function in Dictionary */
if (kvp != null)
{
kvp.Value ;
}
else
{
dict.Add(c, 0);
}
This is possible using linq:
var kvp = dict.SingleOrDefault(x => x.Key == c);
but what about performance?
CodePudding user response:
As noted in comments, finding a key in a dictionary doesn't mean iterating over the whole dictionary. But in some cases it's still worth trying to reduce the lookups.
KeyValuePair<,>
is a struct anyway, so if GetKeyValuePair
did exist, your kvp.Value
wouldn't compile (as Value
is read-only) and wouldn't work even if it did (as the pair wouldn't be the "original" in the dictionary).
You can use TryGetValue
to reduce this to a single "read" operation and a single "write" operation:
// value will be 0 if TryGetValue returns false
if (dict.TryGetValue(c, out var value))
{
value ;
}
dict[c] = value;
Or change to ConcurrentDictionary
and use AddOrUpdate
to perform the change in a single call.
CodePudding user response:
You could also store a reference type in the dict. This means an extra allocation when you insert an item, but you can mutate items without another dictionary access. You'll need a profiler to tell you whether this is a net improvement!
class IntBox
{
public int Value { get; set; }
}
if (dict.TryGetValue(c, out var box))
{
box.Value ;
}
else
{
dict[c] = new IntBox();
}
CodePudding user response:
With .NET 6 you can use CollectionsMarshal.GetValueRefOrAddDefault
for a single lookup:
ref int value = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, c, out bool exists);
if(exists) value ; // changes the value in the dictionary even if it's a value type