EDIT by: @scalablecory
@CornedBee I'd like to get this suggestion to a point where it can be reviewed as a proposal. What do you think of this?
@safern what do you think?
Rationale and Usage
ConcurrentDictionary has an implementation of "remove if" hidden away inside of the private ICollection<T>.Remove. Usage of this looks like:
void DecrementCount(ConcurrentDictionary<string, int> dict, string key)
{
while(true)
{
int current = dict[key];
if(current != 1)
{
if(dict.TryUpdate(key, current - 1, current))
{
return;
}
}
else
{
ICollection<KeyValuePair<string, int>> col = dict;
if(col.Remove(new KeyValuePair<string, int>(key, current)))
{
return;
}
}
}
}
This proposal is to expose this existing feature in a more friendly and discoverable API. New usage:
void DecrementCount(ConcurrentDictionary<string, int> dict, string key)
{
while(true)
{
int current = dict[key];
if(current != 1)
{
if(dict.TryUpdate(key, current - 1, current))
{
return;
}
}
else
{
if(dict.TryRemoveIf(key, current))
{
return;
}
}
}
}
The discoverability issue:
Proposed API
class ConcurrentDictionary<>
{
public bool TryRemoveIf(TKey key, TValue comparisonValue);
}
Approved API
class ConcurrentDictionary<>
{
public bool TryRemove(KeyValuePair<TKey key, TValue> keyValuePair);
}
Details
- This brings parity with
TryUpdate.
- Unfortunately we have a wart: the existing
TryRemove does not compare a value, but TryUpdate does. TryRemoveIf will therefor have naming inconsistent with one of them.
Open Questions
- Should we make the existing method public? Not sure if reasonable API signature.
- It could be useful to return a type other than
bool that indicates one of: removed, key not found, or value not found.
- It could also be useful to return the original
value in an out param.
- This would avoid rehashing in a spin loop. Existing implementation inside of
ConcurrentDictionary has spin loops that do this, so the feature already exists and it's clearly a useful optimization, but avoiding rehashing in general is a larger discussion.
- It would also allow some cleanup if the value removed is "equal" but not the same instance, though I'm not sure how common this is.
- This API, along with the existing
TryUpdate, has a non-obvious complication in that it only uses the default comparer for a type: the example above would not work as the user intended if MyState is a class that hasn't overridden Equals. It may be worth trying to make this easier to use.
- There's worry that
TryRemoveIf's use case has greater exposure to misuse, and it may be worth keeping it hidden to avoid that.
- Do we want to support custom comparers for values? I can't see a big use case for this, and if we need more flexibility here I would prefer a general
TryRemoveIf(TKey key, Func<TKey, TValue, object, bool> comparer, object state).
EDIT by: @scalablecory
@CornedBee I'd like to get this suggestion to a point where it can be reviewed as a proposal. What do you think of this?
@safern what do you think?
Rationale and Usage
ConcurrentDictionaryhas an implementation of "remove if" hidden away inside of the privateICollection<T>.Remove. Usage of this looks like:This proposal is to expose this existing feature in a more friendly and discoverable API. New usage:
The discoverability issue:
Proposed API
Approved API
Details
TryUpdate.TryRemovedoes not compare a value, butTryUpdatedoes.TryRemoveIfwill therefor have naming inconsistent with one of them.Open Questions
boolthat indicates one of: removed, key not found, or value not found.valuein an out param.ConcurrentDictionaryhas spin loops that do this, so the feature already exists and it's clearly a useful optimization, but avoiding rehashing in general is a larger discussion.TryUpdate, has a non-obvious complication in that it only uses the default comparer for a type: the example above would not work as the user intended ifMyStateis aclassthat hasn't overriddenEquals. It may be worth trying to make this easier to use.TryRemoveIf's use case has greater exposure to misuse, and it may be worth keeping it hidden to avoid that.TryRemoveIf(TKey key, Func<TKey, TValue, object, bool> comparer, object state).