As @SLaks indicated, your code has a race condition. ConcurrentDictionary
was built to prevent such scenarios by providing methods that perform complex operations atomically, such as GetOrAdd
and AddOrUpdate
.
Here's a sequence that describes how your code could break:
- Thread 1 executes
TryGetValue
which returns false
- Thread 2 does the same
- Both threads do the calculation
- Thread 2 adds the value to the dictionary and returns the result
- Thread 1 either
- (using the indexer setter) adds another value, overwriting the previous one and returns it
- (using
TryAdd
) doesn't add it and the method returns a result that's potentially different than the one in the dictionary
- Thread 2 now has a result that's potentially different than the one in the dictionary
So you can see how not using GetOrAdd
makes things a lot more complex and could have potentially disastrous results. It's possible that in your case if you're deterministically calculating a string from the key it won't matter - but there's really no reason not to use this method. It also simplifies the code, and may (marginally) improve performance since it calculates the hash-code just once.
Another conclusion is choosing between the indexer and TryAdd
is much more a question of correctness than of performance.
That said, the performance of both the indexer []
and TryAdd
methods is identical, since both share the same implementation (TryAddInternal
), which works by taking the lock belonging to the key's bucket, then checking if the key exists in the dictionary, and either updating the dictionary or not.
Lastly, here's an example that shows how to build methods like GetOrAdd
correctly.