How To Update Dictionary Values In C#

How To Update Dictionary Values Banner Image

Introduction

In C#, once a dictionary is intialized and have some data in it. You may want to update the values. There could be a counter or tracking some text from user input or have a flag in the dictionary value. All of these values can be updated in the dictionary if it isn't in a loop.

Video Code Walkthrough

Summary Table

Overall RankMethodSpeed
1TryGetValue And Indexer32ms
2ContainsKey and Indexer40ms
3ContainsKey Then Indexer and Increment41ms
4TryAdd41ms

Can A Dictionary Values Be Changed?

There is only one way update the value which by the indexer. The indexer is where the key is passed and the return item is the value. Below the sample code to update or insert a dictionary value.

Example Dictionary Indexer Code

Dictionary<string, string> dateMonthDictionary = new Dictionary<string, string>();
string key = "1/20/2020";//starting key
string value = "Jan";//starting value;
string updatedValue = "January";//updated value;
dateMonthDictionary[key] = value;//First entry of this key so it is an add function

Console.WriteLine($"key:{key}, value:{dateMonthDictionary[key]}");

dateMonthDictionary[key] = updatedValue;//The key already exists so it is an update on that value

Console.WriteLine($"key:{key}, value:{dateMonthDictionary[key]}");
Code Output
key:1/20/2020, value:Jan
key:1/20/2020, value:January

This indexer on the first useage does an add since the key does not exist yet. The key and value are added to the dictionary. Then on the second time, the indexer is used it updates the value of that key.

But there are multiple ways to check and get the value from the dictionary to update it. They are through the indexer. Then there are variations that do checks on the keys such as ContainsKey, TryGetValue, and TryAdd. These give indications that that is safe to use a key in the dictionary.

Indexer Key Not Found Exception

If we access or use a key in the dictionary that does not exist yet then an exception is thrown saying key not found in the dictionary. THis means if we use the indexer on the right side of the equal's sign and we haven't added that key value pair in the dictionary then the error comes. Below is example of that error being thrown.

Dictionary<string, int> transationsPerDay = new Dictionary<string, int>();
string key = "1/20/2020";//starting key
int value = 1;
transationsPerDay[key] = transationsPerDay[key] + value;//Trying to access the value before the key value pair has been added yet.
Code Output
System.Collections.Generic.KeyNotFoundException: 'The given key '1/20/2020' was not present in the dictionary.'

Cases like this are the reason why we are through the next methods that add addtional protection. If you want to know more about what the best practices are for dictionary I created another article that goes into more detail called Dictionary Best Practices And What To Avoid

TryGetValue And Indexer

TryGetValue Method Chart

TryGetValue is used as an check and to get the value. If it succeeds in getting the value it returns the bool true. We place this into an if statement then update the value and assign it in the dictionary.

Dictionary<string, string> dateMonthDictionary = new Dictionary<string, string>();
string key = "1/20/2020";//starting key
string value = "Jan";//starting value;
dateMonthDictionary[key] = value;//First entry of this key so it is an add function

Console.WriteLine($"key:{key}, value:{dateMonthDictionary[key]}");
if(dateMonthDictionary.TryGetValue(key, out string monthName))
{
    dateMonthDictionary[key] = monthName + " 1 2020";  //The key already exists so it is an update on that value
}

Console.WriteLine($"key:{key}, value:{dateMonthDictionary[key]}");
Code Output
key:1/20/2020, value:Jan
key:1/20/2020, value:Jan 1 2020

TryGetValue And Indexer Speed Test

Next to test this sequence's performance, I have to take test parameters to the large numbers. The time is measured on average of 10 tests with each test loop through the method 1 million times and each method will have 1 million objects for data. Since dictionary has very fast indexer operations this is why test parameters have to be so large. Let's look at the test code.

using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 1000000;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//1 million test objects
string testName = "TryGetValue And Indexer";//Test name to print to average
string key = "Jan";

void TestMethod(Dictionary<string, int> dictionary)
{
    if (dictionary.TryGetValue(key, out int count))
    {
        dictionary[key] = count + 1;  //The key already exists so it is an update on that value
    }
}

List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");
Supporting Code
double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, int> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, int> GetDictionaryData()
{
    Dictionary<string, int> testData = new Dictionary<string, int>();

    Dictionary<int, string> monthlyDictionary = new Dictionary<int, string>() { { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "April" }, { 5, "May" }, { 6, "Jun" }, { 7, "July" }, { 8, "Aug" }, { 9, "Sept" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" } };//initialize dictionary
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        string month = monthlyDictionary[date.Month];
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = 1;
        }
    }
    if (!testData.ContainsKey("Jan"))
    {
        testData.Add("Jan", 1);
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
Code Output
Function calls:1000000, In 0m 0s 32ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 32ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
Function calls:1000000, In 0m 0s 31ms
TryGetValue And Indexer Average speed:32ms, In 10 tests

This it the result of the test with TryGetValue and Indexer completes the test in 32ms. This will be the baseline speed to compare against the rest of the methods.

ContainsKey and Indexer Method

ContainsKey Method Chart

Another way we can check if a key exists is to use ContainsKey. It does not get return the value but only checks if the key exists and returns true or false based on that. But in this context, I can use the ContainsKey to check to see if the key already exists then use the indexer to get the value then update the value.

Dictionary<string, int> dateCountDictionary = new Dictionary<string, int>();
string key = "1/20/2020";//starting key
dateCountDictionary[key] = 1;//First entry of this key so it is an add function

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");
if (dateCountDictionary.ContainsKey(key))
{
    dateCountDictionary[key] = dateCountDictionary[key] + 1;  //The key already exists so it is an update on that value
}

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");
Code Output
key:1/20/2020, value:1
key:1/20/2020, value:2

ContainsKey and Indexer Speed Test

This will be the same testing parameters as the other test. Check out the results below.

using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 1000000;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//1 million test objects
string testName = "ContainsKey and Indexer";//Test name to print to average
string key = "Jan";

void TestMethod(Dictionary<string, int> dictionary)
{
    if (dictionary.ContainsKey(key))
    {
        dictionary[key] = dictionary[key] + 1;  //The key already exists so it is an update on that value
    }
}

List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");
Supporting Code

double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, int> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, int> GetDictionaryData()
{
    Dictionary<string, int> testData = new Dictionary<string, int>();

    Dictionary<int, string> monthlyDictionary = new Dictionary<int, string>() { { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "April" }, { 5, "May" }, { 6, "Jun" }, { 7, "July" }, { 8, "Aug" }, { 9, "Sept" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" } };//initialize dictionary
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        string month = monthlyDictionary[date.Month];
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = 1;
        }
    }
    if (!testData.ContainsKey("Jan"))
    {
        testData.Add("Jan", 1);
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
Code Output
Function calls:1000000, In 0m 0s 41ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 40ms
ContainsKey and Indexer Average speed:40ms, In 10 tests

We can see from this result that this a slower result than using TryGetValue but it is still pretty fast.

ContainsKey, Indexer and Increment Method

Another to way to update the dictionary value is add the increment operation to the dictionary itself. See code below.

Dictionary<string, int> dateCountDictionary = new Dictionary<string, int>();
string key = "1/20/2020";//starting key
dateCountDictionary[key] = 1;//First entry of this key so it is an add function

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");
if (dateCountDictionary.ContainsKey(key))
{
    dateCountDictionary[key]++;  //The key already exists so it is an update on that value
}

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");

Code Output
key:1/20/2020, value:1
key:1/20/2020, value:2

This provides the same result with less code.

ContainsKey, Indexer and Increment Method

I will preform the same test as before with the same parameters.

using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 1000000;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//1 million test objects
string testName = "ContainsKey, Indexer and Increment";//Test name to print to average
string key = "Jan";

void TestMethod(Dictionary<string, int> dictionary)
{
    if (dictionary.ContainsKey(key))
    {
        dictionary[key]++;  //The key already exists so it is an update on that value
    }
}

List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");
Supporting Code
double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, int> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, int> GetDictionaryData()
{
    Dictionary<string, int> testData = new Dictionary<string, int>();

    Dictionary<int, string> monthlyDictionary = new Dictionary<int, string>() { { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "April" }, { 5, "May" }, { 6, "Jun" }, { 7, "July" }, { 8, "Aug" }, { 9, "Sept" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" } };//initialize dictionary
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        string month = monthlyDictionary[date.Month];
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = 1;
        }
    }
    if (!testData.ContainsKey("Jan"))
    {
        testData.Add("Jan", 1);
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
Code Output
Function calls:1000000, In 0m 0s 41ms
Function calls:1000000, In 0m 0s 42ms
Function calls:1000000, In 0m 0s 41ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 43ms
Function calls:1000000, In 0m 0s 40ms
ContainsKey, Indexer and Increment Average speed:41ms, In 10 tests

Using this method we can shorten the code and not lose any performance as the previous example. The difference in perfomance is only 1ms which wouldn't be noticable.

TryAdd Method

TryAdd Method Chart

With this method will try to add a dictionary key and value pair to the dictionary. Then it returns a true or false bool if the operation was successful. This also gives us an indication to whether or not the key exists in the dictionary and is therefore safe to access that key. Sample code below.

Dictionary<string, int> dateCountDictionary = new Dictionary<string, int>();
string key = "1/20/2020";//starting key
dateCountDictionary[key] = 1;//First entry of this key so it is an add function

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");
if (!dateCountDictionary.TryAdd(key,1))//try to add but the key would have already exists
{
    dateCountDictionary[key] += 1;  //The key already exists so it is an update on that value
}

Console.WriteLine($"key:{key}, value:{dateCountDictionary[key]}");
Code Output
key:1/20/2020, value:1
key:1/20/2020, value:2

This result is as expected. It works. Next we need to see how this method performs.

TryAdd Speed Test

This will the be the same test as in the previous examples so that it can be compared. Let's look at the code now.


using System.Diagnostics;
int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 1000000;//Number of function calls made per test
int numberOfObjectsToCreate = 1000000;//1 million test objects
string testName = "TryAdd";//Test name to print to average
string key = "Jan";

void TestMethod(Dictionary<string, int> dictionary)
{
    if (!dictionary.TryAdd(key, 1))//try to add but the key would have already exists
    {
        dictionary[key] += 1;  //The key already exists so it is an update on that value
    }
}

List<double> testSpeedList = new List<double>();
for (int i = 0; i < numberOfTests; i++)
{
    testSpeedList.Add(StartTest());
}
Console.WriteLine($"{testName} Average speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} tests");
Supporting Code
double StartTest()
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, int> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Function calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, int> GetDictionaryData()
{
    Dictionary<string, int> testData = new Dictionary<string, int>();

    Dictionary<int, string> monthlyDictionary = new Dictionary<int, string>() { { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "April" }, { 5, "May" }, { 6, "Jun" }, { 7, "July" }, { 8, "Aug" }, { 9, "Sept" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" } };//initialize dictionary
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        string month = monthlyDictionary[date.Month];
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = 1;
        }
    }
    if (!testData.ContainsKey("Jan"))
    {
        testData.Add("Jan", 1);
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
Code Output
Function calls:1000000, In 0m 0s 44ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 39ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 40ms
Function calls:1000000, In 0m 0s 39ms
TryAdd Average speed:41ms, In 10 tests

TryAdd is in line with the some of the other methods which right around 40ms.

Conclusion

The best method for updating a dictionary is the TryGetValue. It does two things at the same, it checks if the key exists in the dictionary. Then it retrives the value and returns the value. This helps with performance and ease of writing the code. Once the value is obtain we can add the update logic. The other methods are pretty much the same perfermance so it wouldn't make much difference if you perfer one over the other.

Do you know of a better way to update a dictionary? Let me know in the commonts.

Get Latest Updates