Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HashSet.Remove not working with EqualityComparer

Tags:

c#

hashset

I am expecting a HashSet that has been created with a specified EqualityComparer to use that comparer on a Remove operation. Especially since the Contains operations returns true!

Here is the code I am using:

public virtual IEnumerable<Allocation> Allocations { get { return _allocations; } }
private ICollection<Allocation> _allocations; 

public Activity(IActivitySubject subject) {    // constructor
    ....
    _allocations = new HashSet<Allocation>(new DurationExcludedEqualityComparer());
}

public virtual void ClockIn(Allocation a)
{
    ...
    if (_allocations.Contains(a)) 
        _allocations.Remove(a);
    _allocations.Add(a);
}

Below is some quick and dirty LINQ that gets me the logic I want, but I am guessing the HashSet remove based on the EqualityComparer would be significantly faster.

public virtual void ClockIn(Allocation a)
{
    ...
    var found = _allocations.Where(x => x.StartTime.Equals(a.StartTime) && x.Resource.Equals(a.Resource)).FirstOrDefault();
    if (found != null)
    {
            if (!Equals(found.Duration, a.Duration))
            {
                found.UpdateDurationTo(a.Duration);
            }
    }
    else
    {
            _allocations.Add(a);
    }

Can anyone suggest why the Remove would fail when the Contains succeeds?

Cheers,
Berryl

=== EDIT === the comparer

public class DurationExcludedEqualityComparer : EqualityComparer<Allocation>
{
    public override bool Equals(Allocation lhs, Allocation rhs)
    {
        if (ReferenceEquals(null, rhs)) return false;
        if (ReferenceEquals(lhs, null)) return false;
        if (ReferenceEquals(lhs, rhs)) return true;

        return 
            lhs.StartTime.Equals(rhs.StartTime) &&
            lhs.Resource.Equals(rhs.Resource) && 
            lhs.Activity.Equals(rhs.Activity);
    }

    public override int GetHashCode(Allocation obj) {
        if (ReferenceEquals(obj, null)) return 0;
        unchecked
        {
            var result = 17;
            result = (result * 397) ^ obj.StartTime.GetHashCode();
            result = (result * 397) ^ (obj.Resource != null ? obj.Resource.GetHashCode() : 0);
            result = (result * 397) ^ (obj.Activity != null ? obj.Activity.GetHashCode() : 0);
            return result;
        }
    }
}

=== UPDATE - FIXED ===

Well, the good news is that HashSet is not broken and works exactly as it should. The bad news, for me, is how incredibly stupid I can be when not being able to see the forest while examining the leaves on the trees!

The answer is actually in the posted code above, if you look at the class creating & owning the HashSet, and then taking another look at the Comparer to find out what is wrong with it. Easy points for the first person to spot it.

Thanks to all who looked at the code!

like image 793
Berryl Avatar asked Nov 14 '22 07:11

Berryl


1 Answers

Well, your code that "works" appears to look at StartTime and Resource while ignoring Activity, whereas your IEqualityComparer<Allocation> implementation looks at all three. Could your problem be related to that?

Also: are your StartTime, Resource, and Activity properties unchanging? Otherwise, since they affect your GetHashCode result, I think you run the risk of breaking your HashSet<Allocation>.

like image 102
Dan Tao Avatar answered Dec 14 '22 23:12

Dan Tao