Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to update the value of a key in a dictionary, but only if the key already exists, without hashing the key twice?

I have a Dictionary<string, decimal> with a fixed number of entries, and I want to update many of its values frequently, but only for keys that already exist. If a key is not already in the dictionary, I don’t want to add it, because my goal is to restrict the dictionary to a fixed size. So the code below (using the set indexer) will not work for my needs:

dictionary[key] = newValue;

This code will update the value of the key if it’s already there, or it will insert a new key/value pair if it’s not. That’s not what I want. So I wrote an extension method TryUpdate for the Dictionary<TKey, TValue> class, with the desirable behavior:

public static bool TryUpdate<TKey, TValue>(
    this Dictionary<TKey, TValue> source,
    TKey key, TValue value)
{
    ArgumentNullException.ThrowIfNull(source);
    if (!source.ContainsKey(key))
        return false;
    source[key] = value;
    return true;
}

Now my code works as expected:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

dictionary.TryUpdate(key, newValue);

What I don’t like to the above implementation is that if the key already exists, which is the common case in my scenario, the key will be hashed twice in each TryUpdate operation. So in order to get the guarantee that my dictionary will not grow beyond its initial size, I am going to pay a doubled price in terms of performance. The keys are long strings, so the hashing cost is significant. Is there any way to rewrite my TryUpdate method, so that it is as performant as the set indexer?

>Solution :

Since .NET 6 there is no need to use wrappers – use CollectionsMarshal.GetValueRefOrNullRef instead:

Gets either a reference to a TValue in the Dictionary<TKey,TValue> or a reference null if it does not exist in the dictionary.

var x = new Dictionary<string, string>{{"test", "1"}};
ref var valueRefOrNullRef = ref CollectionsMarshal.GetValueRefOrNullRef(x, "test");
if (!Unsafe.IsNullRef(ref valueRefOrNullRef))
{
    valueRefOrNullRef = "2";
}

Console.WriteLine(x["test"]); // prints 2

UPD

Full implementation:

public static bool TryUpdate<TKey, TValue>(
    this Dictionary<TKey, TValue> source,
    TKey key,
    TValue value)
    where TKey : notnull
{
    ArgumentNullException.ThrowIfNull(source);
    ref var valueRefOrNullRef = ref CollectionsMarshal.GetValueRefOrNullRef(source, key);
    if (Unsafe.IsNullRef(ref valueRefOrNullRef))
    {
        return false;
    }

    valueRefOrNullRef = value;
    return true;
}

var x = new Dictionary<string, decimal> { { "test", 1 } };
Console.WriteLine(x.TryUpdate("test", 2)); // True
Console.WriteLine(x.TryUpdate("test1", 2)); // False
Console.WriteLine(x["test"]); // 2
Console.WriteLine(x.ContainsKey("test2")); // False

Note that this approach has next limitation (compared to the wrapper one):

Items should not be added or removed from the Dictionary<TKey,TValue> while the ref TValue is in use.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading