Dictionary for locking on unique identifiers without leaking memory The following code is greatly inspired on this question and this answer. That answer works quite well for a specific case, but I was looking for a solution that:


could be used more generic (so I can put this in a base library and never have to think about it anymore).
got rid of the Find() and replace it with a O1 dictionary lookup. To be honest I doubt if this would have any performance effect, but it was tickling my OCDs.


I've tried to test it with running about 10 threads and superficially it seems to work. But as concurrency is hard to test and reason about - am I missing something? Any other obvious upgrades?

public class LockDictionary&lt;TKey&gt;
{
    object _RootLock = new object();
    Dictionary&lt;TKey,ItemLock&gt; _KeyToLock = new Dictionary&lt;TKey,ItemLock&gt;();


    public IDisposable Lock(TKey key)
    {
        ItemLock item_lock;

        lock  ( _RootLock )
        {
            item_lock = GetOrCreate(key);
            item_lock._Counter++;
        }

        Monitor.Enter(item_lock);

        return new LockCounter(this, key);
    }


    private ItemLock GetOrCreate(TKey key)
    {
        if ( !_KeyToLock.TryGetValue(key, out var item_lock) )
        {
            item_lock = new ItemLock();
            _KeyToLock.Add( key, item_lock );
        }

        return item_lock;
    }


    private void ReleaseLock(TKey key)
    {
        lock (_RootLock)
        {
            var item_lock = _KeyToLock[key];
            item_lock._Counter--;

            if ( item_lock._Counter == 0 )
                _KeyToLock.Remove(key);

            Monitor.Exit(item_lock);
        }
    }


    private class ItemLock
    {
        public object   _ItemLock   = new object();
        public int      _Counter    = 0;
    }


    private class LockCounter : IDisposable
    {
        LockDictionary&lt;TKey&gt; _Parent;
        TKey _Key;


        public LockCounter(LockDictionary&lt;TKey&gt; parent, TKey key)
        {
            _Parent     = parent;
            _Key        = key;
        }


        public void Dispose()
        {
            _Parent.ReleaseLock(_Key);
        }
    }
}


Update

To clarify; I'm looking for an implementation for the following:

// minimal definition boilerplate
// possibility of using other types as key, like string, long or a struct
static LockDictionary&lt;int&gt; _LockDictionary = new LockDictionary&lt;int&gt;();

// minimal boilerplate for having a separate lock per "groupId"
using ( _LockDictionary.Lock(groupId) )
{
     // can only be run once concurrently per unique groupId
}
ItemLock._ItemLock is not used anywhere. Also, as a side note, I'm not a fan of using _NamesLikeThis for public items
Using a single lock may not be the best idea performance-wise (though heavily scenario-dependent). Consider using CuncurrentDictionary (which of course brings all sorts of new propblems, but this is how concurrency works). Also, you may consider using a ReadWriteLockSlim, it might be more performant in some scenarios
ReleaseLock is not safe if not matched by a Lock call. Which might happen for example in case of a ThreadAbort. An ugly, but widespread way of dealing with it is placing the critical code to a finally block. This article from the SQL Server people might also be of interest: https://docs.microsoft.com/en-us/dotnet/framework/performance/reliability-best-practices