Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom equality comparer on ConcurrentDictionary TryUpdate #25559

Closed
skarllot opened this issue Mar 21, 2018 · 9 comments
Closed

Allow custom equality comparer on ConcurrentDictionary TryUpdate #25559

skarllot opened this issue Mar 21, 2018 · 9 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Collections backlog-cleanup-candidate An inactive issue that has been marked for automated closure.
Milestone

Comments

@skarllot
Copy link

The method TryUpdate from ConcurrentDictionary should allow to set a custom equality comparer for the value.

Rationale and Usage

A custom equality comparer for TryUpdate can target three problems.

Ahead Of Time

On some platforms is demanded static compilation ahead of time. On those platforms code generation is not allowed.
When the method TryUpdate use EqualityComparer<T>.Default for value type comparison, it attempts to use Reflection to instantiate a new type which implements the IEqualityComparer<T> interface. That operation is not allowed on AOT compiled code.

Performance

Currently the method TryUpdate uses the equality comparer instance from EqualityComparer<T>.Default which not be the most optimized comparison for the case.

Custom comparison

For external types not controlled by the user that does not implement the intended comparison it is needed to create a wrapper to overcome the lack of a proper comparison.
The same is true if a qualified comparison is needed for specific TryUpdate call.

Usage

Given those types:

readonly struct Person : IEquatable<Person>
{
    public long Id { get; }
    public string Name { get; }
    public string Email { get; }
    public int Version { get; }

    public Person(int version)
    {
        Id = default;
        Name = default;
        Email = default;
        Version = version;
    }

    public bool Equals(Person other) => Id == other.Id && Name == other.Name && Email == other.Email && Version == other.Version;
    public override bool Equals(object obj) => obj is Person other && Equals(other);
    public override int GetHashCode() => Id.GetHashCode();
}

sealed class VersioningComparer : IEqualityComparer<Person>
{
    public bool Equals(Person first, Person second) => first.Version == second.Version;
    public int GetHashCode(Person obj) => obj.Version;
}

Can be used as:

private readonly ConcurrentDictionary<long, Person> personDictionary;
private readonly VersioningComparer versionComparer;
// ...
public bool UpdatePerson(Person value)
{
    Person previousVersion = new Person(value.Version - 1);
    return personDictionary.TryUpdate(value.Id, value, previousVersion, versionComparer);
}

Proposed API

public class ConcurrentDictionary<TKey, TValue>
{
// ...
    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue);
    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue, System.Collections.Generic.IEqualityComparer<TValue> comparer);
// ...
}

Pull Request

A PR with the proposed changes is available: dotnet/corefx#28142

@skarllot
Copy link
Author

skarllot commented Jun 2, 2018

I've rebased my PR and ran the tests

  xUnit.net console test runner (64-bit .NET Core)
  Copyright (C) 2014 Outercurve Foundation.

  Discovering: System.Collections.Concurrent.Tests
  Discovered:  System.Collections.Concurrent.Tests
  Starting:    System.Collections.Concurrent.Tests
  Finished:    System.Collections.Concurrent.Tests

  === TEST EXECUTION SUMMARY ===
     System.Collections.Concurrent.Tests  Total: 2079, Errors: 0, Failed: 0, Skipped: 0, Time: 16,444s
  ----- end 13:53:48,81 ----- exit code 0 ----------------------------------------------------------

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2020
@layomia layomia modified the milestones: 5.0.0, Future Jun 24, 2020
@ghost
Copy link

ghost commented Oct 26, 2021

Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process.

This process is part of the experimental issue cleanup initiative we are currently trialing in a limited number of areas. Please share any feedback you might have in the linked issue.

@skarllot
Copy link
Author

I think that this issue is still relevant

@ghost ghost removed the no-recent-activity label Oct 26, 2021
@eiriktsarpalis
Copy link
Member

Hi @skarllot, and apologies for the delayed response.

Doesn't passing the equality comparer at the constructor level address your use case?

I don't believe passing a custom equality comparer on a per-operation basis is practical or a good idea since:

  • It would be impossible to look up an entry with the same key without making sure that the very same equality comparer is passed to the lookup operation.
  • It would likely corrupt the underlying hashtable since it's possible for buckets to contain entries with invalid hashcodes under the default equality semantics for the instance.
  • It's not clear how rehash operations would work for entries inserted using custom comparers.

In light of the above, I would not supporting adding such a feature to ConcurrentDictionary.

@skarllot
Copy link
Author

Hello @eiriktsarpalis, the issue is that the TryUpdateInternal method uses default equality compare instead of the one passed to the constructor.

@eiriktsarpalis
Copy link
Member

My apologies, I misunderstood your request as exposing a key equality comparer parameter. On quick inspection, it seems that the ConcurrentDictionary implementation is hardcoding EqualityComparer<TValue>.Default in quite a few places, so if we do parameterize it should be done on the constructor level.

You mention performance as a motivating factor for this request. Could you share any benchmarks showing performance improvements of your prototype? I should point out that since dotnet/coreclr#14125 the JIT will devirtualize EqualityComparer.Default.Equals calls so there might be an opportunity to slightly improve performance specifically in the TryUpdateInternal implementation.

cc @stephentoub

@skarllot
Copy link
Author

I've mention performance because I could compare instances based on version number instead of all members values.

@ghost ghost added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed needs more info labels Oct 29, 2021
@eiriktsarpalis
Copy link
Member

I suspect that this proposal might cover your use case.

@eiriktsarpalis eiriktsarpalis added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration labels Oct 29, 2021
@ghost
Copy link

ghost commented Nov 1, 2021

This issue has been marked with the api-needs-work label. This may suggest that the proposal requires further refinement before it can be considered for API review. Please refer to our API review guidelines for a detailed description of the process.

When ready to submit an amended proposal, please ensure that the original post in this issue has been updated, following the API proposal template and examples as provided in the guidelines.

@eiriktsarpalis eiriktsarpalis removed the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Nov 1, 2021
@eiriktsarpalis eiriktsarpalis added the backlog-cleanup-candidate An inactive issue that has been marked for automated closure. label Feb 18, 2022
@skarllot skarllot closed this as not planned Won't fix, can't repro, duplicate, stale Dec 3, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Jan 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Collections backlog-cleanup-candidate An inactive issue that has been marked for automated closure.
Projects
None yet
Development

No branches or pull requests

5 participants