Skip to content

Add RemoveIfValue to ConcurrentDictionary #23909

@CornedBee

Description

@CornedBee

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).

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions