Skip to main content

Because the ConcurrentDictionary class includes methods which are not fully atomic. The overloads of GetOrAdd and AddOrUpdate methods which accept delegates as parameters will invoke these delegates outside the synchronization locks.

namespace DotnetCommon.DataStructures
{
    using System.Collections.Generic;
    using System.Threading;

    /// <summary>
    /// Generic Atomic (thread-safe) Dictionary.
    ///
    /// Because the ConcurrentDictionary&lt;TKey, TValue&gt; class includes methods which
    /// are not fully atomic. The overloads of GetOrAdd and AddOrUpdate methods which
    /// accept delegates as parameters will invoke these delegates outside the
    /// synchronization locks.
    /// </summary>
    /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
    /// <remarks>
    ///     <list type="bullet">
    ///         <item><description>Ref: https://www.dotnetcurry.com/csharp/1466/csharp-dotnet-collection-class</description></item>
    ///         <item><description>Also see: https://github.com/microsoft/FASTER/blob/master/cs/src/core/Utilities/SafeConcurrentDictionary.cs</description></item>
    ///         <item><description>Snippet: https://jonlabelle.com/snippets/view/csharp/genericatomic-thread-safe-dictionary-class</description></item>
    ///     </list>
    /// </remarks>
    public class AtomicDictionary<TKey, TValue>
    {
        private readonly ReaderWriterLockSlim _dictionaryLock = new ReaderWriterLockSlim();
        private readonly Dictionary<TKey, TValue> _dictionary;

        public AtomicDictionary()
        {
            _dictionary = new Dictionary<TKey, TValue>();
        }

        public TValue this[TKey key]
        {
            get
            {
                _dictionaryLock.EnterReadLock();
                try
                {
                    return _dictionary[key];
                }
                finally
                {
                    _dictionaryLock.ExitReadLock();
                }
            }
            set
            {
                _dictionaryLock.EnterWriteLock();
                try
                {
                    _dictionary[key] = value;
                }
                finally
                {
                    _dictionaryLock.ExitWriteLock();
                }
            }
        }

        public void Add(TKey key, TValue value)
        {
            _dictionaryLock.EnterWriteLock();
            try
            {
                _dictionary.Add(key, value);
            }
            finally
            {
                _dictionaryLock.ExitWriteLock();
            }
        }

        // ------------------------------------------------------------------------
        // Additional prop/methods below taken from:
        // https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim#examples
        // ------------------------------------------------------------------------

        public int Count
        {
            get
            {
                _dictionaryLock.EnterReadLock();
                try
                {
                    return _dictionary.Count;
                }
                finally
                {
                    _dictionaryLock.ExitReadLock();
                }
            }
        }

        public AddOrUpdateStatus AddOrUpdate(
            TKey key,
            TValue value,
            IEqualityComparer<TValue> valueEqualityComparer = null
        )
        {
            valueEqualityComparer ??= EqualityComparer<TValue>.Default;

            _dictionaryLock.EnterUpgradeableReadLock();
            try
            {
                TValue result;
                if (_dictionary.TryGetValue(key, out result))
                {
                    if (valueEqualityComparer.Equals(result, value))
                    {
                        return AddOrUpdateStatus.Unchanged;
                    }
                    else
                    {
                        _dictionaryLock.EnterWriteLock();
                        try
                        {
                            _dictionary[key] = value;
                        }
                        finally
                        {
                            _dictionaryLock.ExitWriteLock();
                        }

                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    _dictionaryLock.EnterWriteLock();
                    try
                    {
                        _dictionary.Add(key, value);
                    }
                    finally
                    {
                        _dictionaryLock.ExitWriteLock();
                    }

                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                _dictionaryLock.ExitUpgradeableReadLock();
            }
        }

        public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        }

        public bool AddWithTimeout(TKey key, TValue value, int timeout)
        {
            if (_dictionaryLock.TryEnterWriteLock(timeout))
            {
                try
                {
                    _dictionary.Add(key, value);
                }
                finally
                {
                    _dictionaryLock.ExitWriteLock();
                }

                return true;
            }

            return false;
        }

        public void Remove(TKey key)
        {
            _dictionaryLock.EnterWriteLock();
            try
            {
                _dictionary.Remove(key);
            }
            finally
            {
                _dictionaryLock.ExitWriteLock();
            }
        }

        ~AtomicDictionary()
        {
            _dictionaryLock?.Dispose();
        }
    }
}

// ---------------------------------------------------------------------------------------------------------
// Another thread-safe generic dictionary class
// Source: https://github.com/madhatter22/LinqToLdap/blob/master/LinqToLdap/Collections/SafeDictionary.cs
// ---------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace LinqToLdap.Collections
{
    internal class SafeDictionary<TKey, TValue>
    {
        private ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
        private Dictionary<TKey, TValue> _internal = new Dictionary<TKey, TValue>();

        ~SafeDictionary()
        {
            var locker = _locker;
            if (locker != null)
            {
                locker.Dispose();
                _locker = locker = null;
            }
            var dictionary = _internal;
            if (dictionary != null)
            {
                dictionary.Clear();
                _internal = dictionary = null;
            }
        }

        public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
        {
            _locker.EnterReadLock();
            try
            {
                if (_internal.ContainsKey(key))
                {
                    return _internal[key];
                }
            }
            finally
            {
                _locker.ExitReadLock();
            }
            var value = valueFactory(key);
            _locker.EnterWriteLock();
            try
            {
                if (!_internal.ContainsKey(key))
                {
                    _internal.Add(key, value);
                }
                return _internal[key];
            }
            finally
            {
                _locker.ExitWriteLock();
            }
        }

        public ReadOnlyDictionary<TKey, TValue> ToReadOnly()
        {
            _locker.EnterReadLock();
            try
            {
                return new ReadOnlyDictionary<TKey, TValue>(_internal.ToDictionary(d => d.Key, d => d.Value));
            }
            finally
            {
                _locker.ExitReadLock();
            }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            _locker.EnterReadLock();
            try
            {
                if (_internal.ContainsKey(key))
                {
                    value = _internal[key];
                    return true;
                }
                value = default;
                return false;
            }
            finally
            {
                _locker.ExitReadLock();
            }
        }

        public void TryAdd(TKey key, TValue value)
        {
            _locker.EnterWriteLock();
            try
            {
                if (!_internal.ContainsKey(key))
                {
                    _internal.Add(key, value);
                }
            }
            finally
            {
                _locker.ExitWriteLock();
            }
        }

        public void AddOrUpdate(TKey key, TValue value)
        {
            _locker.EnterWriteLock();
            try
            {
                _internal[key] = value;
            }
            finally
            {
                _locker.ExitWriteLock();
            }
        }
    }
}