I want a thread safe list with a max item count, will the following implementation correctly provide this?
public class ConcurrentThrottledList<T>
{
private readonly List<T> items = new List<T>();
private readonly int maxItems;
private object _lock = new object();
public ConcurrentThrottledList(int maxItems)
{
this.maxItems = maxItems;
}
public bool TryAdd(T item)
{
lock (_lock)
{
if (items.Count < maxItems)
{
items.Add(item);
return true;
}
return false;
}
}
public bool TryRemove(T item)
{
lock (_lock)
{
return items.Remove(item);
}
}
public bool IsFull { get { lock (_lock) { return items.Count >= maxItems; } } }
public T[] Items { get { lock(_lock) { return items.ToArray(); } } }
}
3 Answers 3
@ChrisW made valuable comments, I'll add mine:
Well done:
- I like that private fields are all1
readonly
, this makes intent explicit, which works towards greater maintainability. - I like that the class is highly cohesive and focused, this works towards greater readability, maintainability and extensibility.
Nitpicks:
I don't see why
_lock
gets an underscore anditems
andmaxItems
don't; being consistent with the underscore prefix for private fields would mootinate thethis
qualifier in the constructor.Items
could be calledToArray
, making it more consistent with the framework's lingo, and more representative of its semantics.TryAdd
andTryRemove
both look like they were named after theTryParse
pattern; a developper looking at the API would then also be expecting anAdd
and aRemove
method:Add
would add the specified item, throw an exception if it can't, and returnvoid
.TryAdd
would add the specified item, returntrue
if successful andfalse
otherwise.Remove
would remove the specified item, throw an exception if it can't, and returnvoid
.TryRemove
would remove the specified item, returntrue
if successful andfalse
otherwise.
However, List<T>.Remove()
is already taking care of returning false
if the specified item cannot be removed - throwing an exception in that case would be utter nonsense. Therefore, I'll second @ChrisW in saying that TryRemove
should be simply called Remove
.
I agree with TryAdd
being called as such, because I would seriously expect an Add
method to return void
, so I'd consider also having an Add
method that throws some CapacityExceededException
(or whatever is more appropriate) when it can't add the specified item.
1 I would be led to believe _lock
could also be made readonly
, but I don't work with enough multithreaded code to know whether that would/could impact anything.
will the following implementation correctly provide this?
Yes, it would appear so.
Minor comments:
- TryRemove could be called Remove (because List.Remove is called Remove).
- IsFull may not be useful (because it may be obsoleted by a subsequent Remove by another thread, before this thread is able to act on the IsFull return value)
- Do you want to be able to remove any element? If not, if you only want to remove the first or last element, then ConcurrentQueue or ConcurrentStack might be better than List (in which case only your TryAdd method would need your own explicit lock).
In addition to the existing answers:
I concur that
IsFull
is probably not all that useful._lock
should bereadonly
Your class is called
ConcurrentThrottledList
but you don't implement any of the collection interfaces. If you want to make it more useful consider implementingIEnumerable<T>
,ICollection<T>
and possiblyIList<T>
.
Count
similarly if isIsFull
is useful. For example in a client/server application to show the clients the (most up-to-date available) number elements in some lists and not just if they are full or not. Currently.Items.Length
would cause memory waste. \$\endgroup\$