After much deliberation on the subject, I decided to introduce breaking changes interface. I know, I know, I don’t do this lightly, in fact I think it’s the third worse thing I could possible do (sorry it does follow adding bugs and semantic changes). So here are my reasons why I thought this was necessary:

  1. Calling Add() was habitual and was seldom, if ever, checking the return value for errors or key collisions.
  2. The return value of Add() reflected two possible states, failure or a key collision, with no way to differentiate between them.
  3. The delegate used in the atomic update method did not provide the key, so every time I used it I had to define an anonymous method to inject the key I was updating.
  4. The boolean return value of Update again signaled numerous possible outcomes, either the key was not found, it failed, or the resulting value was equal to the existing value.
  5. Lastly, I found and reviewed the ConcurrentDictionary interface and found it exactly matched my needs.

So as much as I hate to do so, I am breaking the interface. I wouldn’t do so if I didn’t wholeheartedly believe that we will both be better off for it. If you have an existing project the conversion is trivial, just insert “Try” after the ‘.’ for all the errors and you’ll be mostly there. If you want to stick to the version you have that should be viable, I’ve made no bug fixes as I haven’t found any. So the following methods have been added, most of these taken from the ConcurrentDictionary:

    void Add(TKey key, TValue value);
    bool TryAdd(TKey key, Converter<TKey, TValue> fnCreate);
    bool TryAdd(TKey key, TValue value);

    TValue GetOrAdd(TKey key, TValue value);
    TValue GetOrAdd(TKey key, Converter<TKey, TValue> fnCreate);

    TValue AddOrUpdate(TKey key, TValue addValue, KeyValueUpdate<TKey, TValue> fnUpdate);
    TValue AddOrUpdate(TKey key, Converter<TKey, TValue> fnCreate, KeyValueUpdate<TKey, TValue> fnUpdate);
    bool AddOrUpdate<T>(TKey key, ref T createOrUpdateValue) where T : ICreateOrUpdateValue<TKey, TValue>;

    bool TryUpdate(TKey key, TValue value);
    bool TryUpdate(TKey key, TValue value, TValue comparisonValue);
    bool TryUpdate(TKey key, KeyValueUpdate<TKey, TValue> fnUpdate);

    bool Remove(TKey key);
    bool TryRemove(TKey key, out TValue value);
    bool TryRemove(TKey key, KeyValuePredicate<TKey, TValue> fnCondition);
    bool TryRemove<T>(TKey key, ref T removeValue) where T : IRemoveValue<TKey, TValue>;

Sorry again for any inconvenience, yet I think you will agree that the final interface is much cleaner and clearer. The following outlines the intended behavior of these methods:

  • All methods now throw on internal failures to avoid ambiguous return values.
  • Boolean methods do not return false when the updated value equals the existing value.

Continue reading: Part 2 – Enumerate Ranges and First/Last