2

I'm using a Syncfusion grid control's selections to get a list of our data objects. When we multi-select objects, for some reason the objects are being double reported. The below code returns one entry for a single selection, four (!) for two selected objects, six (!) for three, etc.

There seems to be a minor bug in the selection code that is causing a multi-selection to be reported twice in this.BaseGridControl.Model.Selections. Yes, the bug should be fixed. But for now, I can work around that; since the objects these rows represent are going into a HashSet anyway, they should be deduplicated there.

HashSet<Bean> selectedBeanSet = new HashSet<Bean>();

for (int i = this.BaseGridControl.Model.Selections.Count - 1; i >= 0; i--) {
    selection = this.BaseGridControl.Model.Selections.Ranges[i];
    topRow = selection.Top;
    bottomRow = selection.Bottom;
    for (int j = bottomRow; j >= topRow; j--) {
        selectedBeanSet.Add(GetObjectAtRow(j));
    }
}

But even after doing this, I still have four rows when there should only be two. Interrogating in the immediate window, I can learn a little about what's there:

selectedBeanSet.Take(4).ToList()[3].GetHashCode()
5177447
selectedBeanSet.Take(4).ToList()[1].GetHashCode()
5177447

These are still the same object! I wrote the .GetHashCode() myself — I know it's referring to properties of Bean and not any kind of reference.

And in case there was any doubt:

selectedBeanSet.GetType().Name
"HashSet`1"

How can the same object be in a HashSet twice? Is the .GetHashCode() I'm calling in the immediate window not the one being used as a key in the HashSet? What's going on here?

EDIT: Here's my .GetHashCode() implementation. I've put a breakpoint in here and stepped through so I know it's getting hit.

public override int GetHashCode() {
    return this.Property1.GetHashCode() ^ this.Property2.GetHashCode() ^ this.Property3.GetHashCode();
}

And .Equals():

public override bool Equals(Bean other) {
    return this.Property1 == other.Property1 && this.Property2 == other.Property2 && this.Property3 == other.Property3;
}

EDIT 2: The Bean class, sanitized for public consumption. It's a bit strange since for persistence reasons we're storing everything in structs behind the scenes. I believe that fact is immaterial to this problem, however.

public class Bean : AbstractBean<Bean, BeanStruct> {
    BeanStruct myStruct;

    public int Property1 {
        get {
            return this.myStruct.property1;
        }
        set {
            this.myStruct.property1 = value;
            RaisePropertyChanged("Property1");
        }
    }
    public string Property2 {
        get {
            return this.myStruct.property2;
        }
        set {
            this.myStruct.property2 = EngineUtils.FillOrTruncate(value, Bean.Length);
            RaisePropertyChanged("Property2");
        }
    }
    public string Property3 {
        get {
            return this.myStruct.property3;
        }
        set {
            this.myStruct.property3 = EngineUtils.FillOrTruncate(value, Bean.Length);
            RaisePropertyChanged("Property3");
        }
    }
}
James Cronen
  • 5,715
  • 2
  • 32
  • 52
  • 6
    Please post your implementation of both `GetHashCode()` and `Equals`. It's entirely reasonable for a hash set to have two values with the same hash code, so long as they're not equal. It's also possible that the hash code changed after it being added to the set, which would also mess things up. Can you provide a short but complete program demonstrating the problem? – Jon Skeet Mar 05 '15 at 17:05
  • 1
    It sounds like your implementation allows objects that are equivalent to each other when compared using the Equals() method to have different hash codes. – Dmitry S. Mar 05 '15 at 17:06
  • Can we see the Bean class? Do those properties have setters? Are you changing the values at runtime? – Dmitry S. Mar 05 '15 at 17:10
  • 2
    It is not a good idea to use properties with setters in the `GetHashCode()` implementation. It can cause the hash code to change after the object has been added to the HashSet instance. – Dmitry S. Mar 05 '15 at 17:17
  • @DmitryS.: Fair enough; however, I interrogated the elements of the `selectedBeanSet` immediately after the fourth and final value was entered. Shouldn't it still have been excluded, since the hash code matched? – James Cronen Mar 05 '15 at 17:21
  • Is using ^ in hashcode function valid ? I mean does it provide a unique hashcode always ? (a point of interest for me at least) – Kavindu Dodanduwa Mar 05 '15 at 17:23
  • 2
    I prefer XOR (^) because each "1" in each operand guarantees a change in the overall result. – James Cronen Mar 05 '15 at 17:25
  • 1
    @KcDoD: I can't comment on the quality of this hashing algorithm. However, it is not possible in principle to provide a unique hashcode for every input. You can only strive to minimize the likelihood of collisions. – Odrade Mar 05 '15 at 18:08
  • Use of the XOR operator to aggregate hash codes is well-understood to be one of the worst ways to accomplish the task. See [What is the best algorithm for an overridden System.Object.GetHashCode?](http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode) for advice on how to do it correctly. As far as your question goes, as others have pointed out, having the same hash code doesn't mean you have the same object. Please provide [a _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve) if you want an answer. – Peter Duniho Mar 06 '15 at 02:21

0 Answers0