Home > OS >  Most efficient way to retrieve a KeyValuePair from Dictionary
Most efficient way to retrieve a KeyValuePair from Dictionary

Time:11-05

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

Demo: https://dotnetfiddle.net/tnW9P5

  •  Tags:  
  • c#
  • Related