1

Recently I switched from HashSet to a collection someone else posted called 'ConcurrentHashSet', I decided to opt out of locking my own HashSet as I was using it a lot and just seemed a safer bet to use a pre-made thread-safe class, but I've hit an issue.

When I used HashSet (the default HashSet class) I was getting my HashSet values using the First and FirstOrDefault methods, the issue is I can no longer use these methods and I'm not sure why or how to re-implement them, is it even possible? It may be really simple, I'm unsure.

I was hoping someone would know and could point me in the right direction. Heres the class that I picked up off another stack overflow answer, although I'm not sure if this is the original.

public class ConcurrentHashSet<T> : IDisposable
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private readonly HashSet<T> _hashSet = new HashSet<T>();

    public bool TryAdd(T item)
    {
        _lock.EnterWriteLock();

        try
        {
            return _hashSet.Add(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void Clear()
    {
        _lock.EnterWriteLock();

        try
        {
            _hashSet.Clear();
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Contains(T item)
    {
        _lock.EnterReadLock();

        try
        {
            return _hashSet.Contains(item);
        }
        finally
        {
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public bool TryRemove(T item)
    {
        _lock.EnterWriteLock();

        try
        {
            return _hashSet.Remove(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public int Count
    {
        get
        {
            _lock.EnterReadLock();

            try
            {
                return _hashSet.Count;
            }
            finally
            {
                if (_lock.IsReadLockHeld) _lock.ExitReadLock();
            }
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _lock?.Dispose();
        }
    }

    ~ConcurrentHashSet()
    {
        Dispose(false);
    }
}
tuzepopaw
  • 13
  • 2
  • Possible duplicate of [Concurrent HashSet in .NET Framework?](https://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework) – mjwills Dec 02 '17 at 09:31
  • Also consider removing `~ConcurrentHashSet()` since it doesn't achieve anything and just slows down GC of the object. – mjwills Dec 02 '17 at 09:39

1 Answers1

2

Internally the custom class you have posted uses a HashSet<T> to store the data. So you can still make use of the methods you mentioned, First and FirstOrDefault, provided that you would do it in a thread safe way. For instance the implementation of FirstOrDefault would have been something like this:

public T TryGetFirstOrDefault()
{
    _lock.EnterReadLock();

    try
    {
        return _hashSet.FirstOrDefault();
    }
    finally
    {
        if (_lock.IsReadLockHeld) _lock.ExitReadLock();
    }
}

Update

You could generalize the above by passing a predicate:

public T TryGetFirstOrDefault(Func<T, bool> predicate)
{
    _lock.EnterReadLock();

    try
    {
        return _hashSet.FirstOrDefault(x=>predicate(x));
    }
    finally
    {
        if (_lock.IsReadLockHeld) _lock.ExitReadLock();
    }
}

So in case you have a ConcurrentHashSet<Player>, you could use it for instance as:

var player = concurrentHashSetOfPlayers.TryGetFirstOrDefault(x=>x.Id == playerId);
Christos
  • 53,228
  • 8
  • 76
  • 108
  • Thanks for the quick response, although is there a way to make this method the same as calling it directly and providing parameters through `TryGetFirstOrDefault` then the original `FirstOrDefault` in the try block? How I did it directly with a HashSet: `return _players.FirstOrDefault(x => x.Id == playerId);` – tuzepopaw Dec 02 '17 at 08:17
  • Very straight forward answer. I've set it as the accepted answer. :) – tuzepopaw Dec 02 '17 at 08:24
  • @tuzepopaw Thanks ! – Christos Dec 02 '17 at 08:24