tags:

views:

73

answers:

1

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!

+1  A: 

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

Dan Tao
@Dan. Bingo on the first line. I'm not quite sure if it was some sort of recursion, but it is an Activity that owns the HashSet in this case. Placing a guard in the add allocation method (ClockIn) to check the allocation belongs to 'this' activity and taking Activity out of the comparer methods is more logical in any event and fixes the problem by returning a consistent hash. Cheers!
Berryl