Attempting to synchronize internally will almost certainly be insufficient because it's at too low a level of abstraction. Say you make the Add and ContainsKey operations individually thread-safe as follows:
public void Add(TKey key, TValue value)
{
lock (this.syncRoot)
{
this.innerDictionary.Add(key, value);
}
}
public bool ContainsKey(TKey key)
{
lock (this.syncRoot)
{
return this.innerDictionary.ContainsKey(key);
}
}
Then what happens when you call this supposedly thread-safe bit of code from multiple threads? Will it always work OK?
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
The simple answer is no. At some point the Add method will throw an exception indicating that the key already exists in the dictionary. How can this be with a thread-safe dictionary, you might ask? Well just because each operation is thread-safe, the combination of two operations is not, as another thread could modify it between your call to ContainsKey and Add.
Which means to write this type of scenario correctly you need a lock outside the dictionary, e.g.
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}
But now, seeing as you're having to write externally locking code, you're mixing up internal and external synchronisation, which always leads to problems such as unclear code and deadlocks. So ultimately you're probably better to either:
Use a normal Dictionary<TKey, TValue> and synchronize externally, enclosing the compound operations on it, or
Write a new thread-safe wrapper with a different interface (i.e. not IDictionary<T>) that combines the operations such as an AddIfNotContained method so you never need to combine operations from it.
(I tend to go with #1 myself)