How to Add or overwrite a value in ConcurrentDictionary in C#
By FoxLearn 3/12/2025 4:02:31 AM 19
Let's explore how to add or overwrite values in a ConcurrentDictionary
using different methods: the indexer, TryAdd(), and AddOrUpdate().
Using the Indexer
The indexer provides the simplest way to unconditionally add or overwrite a value. If the key doesn't exist, it will be added; if it does exist, it will be overwritten. The operation is thread-safe, meaning that multiple threads can access and modify the dictionary without causing issues.
var sessionMap = new ConcurrentDictionary<int, UserSession>(); // Add a new session sessionMap[101] = new UserSession(); // Overwrite an existing session sessionMap[101] = new UserSession();
This is a straightforward way to manage data, especially if you don't care about the existing value or need to overwrite it every time.
When to Use TryAdd()
The TryAdd()
method adds a new key/value pair only if the key doesn't already exist. It returns true
if it was successful in adding the item and false
if the key was already present. This is useful when you want to ensure that you're not overwriting any existing data.
Consider this scenario where you're managing user sessions, and you want to prevent overwriting an existing session:
if (!sessionMap.TryAdd(sessionId, new UserSession())) { throw new SessionExistsException(); }
Compare that with this thread-unsafe version:
if (!sessionMap.ContainsKey(sessionId)) { sessionMap[sessionId] = new UserSession(); } else { throw new SessionExistsException(); }
In the second example, there’s a race condition because ContainsKey()
could return false
, but before the session is added, another thread could have already added it. TryAdd()
eliminates this issue by ensuring that the addition of a new item is atomic and thread-safe.
When to Use AddOrUpdate()
The AddOrUpdate()
method behaves differently. If the key doesn't exist, it adds the key/value pair. But if the key already exists, it calls a provided delegate (updateValueFactory
) to modify the existing value. This makes it ideal when you need to update an existing entry based on its current state.
For instance, imagine you are tracking the number of messages a user has sent in a messaging app, and multiple threads are responsible for updating this count:
var messageCountMap = new ConcurrentDictionary<int, int>(); messageCountMap.AddOrUpdate(userId, 1, (key, currentValue) => currentValue + 1);
In this case:
- If
userId
doesn’t exist, it sets the value to1
. - If
userId
already exists, it increments the current value by1
.
Warning: updateValueFactory
May Run Multiple Times
When multiple threads call AddOrUpdate()
concurrently, the delegate (updateValueFactory
) could execute multiple times before succeeding. This happens because AddOrUpdate()
tries to update the value and, if unsuccessful, retries the operation until it succeeds. If two threads try to update the same key concurrently, they could both execute the delegate repeatedly.
var userMessages = new ConcurrentDictionary<int, int>(); userMessages.TryAdd(1, 0); var allTasks = new List<Task>(); for (int i = 0; i < 10; i++) { int taskId = i; allTasks.Add(Task.Run(() => { userMessages.AddOrUpdate(1, 0, (key, currentValue) => { Console.WriteLine($"Task {taskId} - Current value: {currentValue}"); return currentValue + 1; }); })); } await Task.WhenAll(allTasks); Console.WriteLine($"Final message count for user 1: {userMessages[1]}");
In this example, multiple tasks concurrently attempt to update the message count for userId = 1
. You’ll see that the delegate (updateValueFactory
) executes multiple times, and each thread works with the current value at the time it accesses it, resulting in multiple increments.
- Global exception event handlers in C#
- Handling Posted Form Data in an API Controller
- How to Add a custom action filter in ASP.NET Core
- How to Get all classes with a custom attribute in C#
- How to Map query results to multiple objects with Dapper in C#
- How to Update appsettings.json in C#
- Injecting ILogger into Dapper Polly Retry Policy in C#
- Properly Disposing HttpContent When Using HttpClient in C#