3

I've simplified my code to the following short example that reproduce the error. I'm trying to bind a value of a derived class to a control. The derived class and the binding logic are:

bindingSource = new BindingSource();
numericUpDown1.DataBindings.Add("Value", bindingSource, nameof(SinglePoint.Val), false, DataSourceUpdateMode.OnPropertyChanged);
bindingSource.DataSource = new[] { new SinglePoint(100) };
[...]
public class SinglePoint : PointList
{
    public SinglePoint(int val) { points.Add(val); }
    public int Val
    {
        get { return points[0]; }
        set
        {
            if (value > 300) value = 300; //(*)
            points[0] = value;
        }
    }
}

As a result, when setting for instance a value of 500 on the NumericUpDown, after leaving it we should see 300 because of line (*). This works if the base class does not implement GetHashCode:

public class PointList
{
    protected List<int> points = new List<int>();
    public PointList() { }
    // UNCOMMENT this and the binding will stop working properly
    //public override int GetHashCode()
    //{
    //    unchecked
    //    {
    //        int hashCode = 17;
    //        foreach (var item in points)
    //            hashCode += 13 * item.GetHashCode();

    //        return hashCode;
    //    }
    //}
}

However, if you try to uncomment the GetHashCode implementation, the binding will break. Meaning that after setting 500 and leaving the NumericUpDown, the control will still show 500, but in reality the underlying value will be 300. Why is this happening and how could I solve it without turning down my custom implementation of GetHashCode?

EDIT

As pointed out by some of you, GetHashCode should not change because it is used by the binding mechanism. This reply to the first part of my question. However, how can I make my PointList class satisfying the other rule by which "if two things are equal (Equals(...) == true) then they must return the same value for GetHashCode()". If I have two PointList objects with the same list (sequence) of points I would like them to be found equal (because of other logics in my code), which should imply having the same hash code. How would you do?

Community
  • 1
  • 1
Mauro Ganswer
  • 1,379
  • 1
  • 19
  • 33

3 Answers3

4

WinForms uses the hashcode of the data object and data member to generate a key, which is used to track the data-binded-object in an internal storage. If you change the hash-calculation so that the hash changes when a data member changes value, then previously-generated-key is not updated with it, and is therefor different than the current key. This breaks the data-binding-mechanism.

Source: http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/BindingContext.cs

Maarten
  • 22,527
  • 3
  • 47
  • 68
  • The real answer. For my use case, I was able to work around this by **not** changing `GetHashCode` on my domain object (so databinding can know it's working with the right instance). Rather I made a dedicated `DomainObjectEqualityComparer : IEqualityComparer` and gave **it** the customized `GetHashCode`. The custom comparer can be passed to Linq functions like `Distinct` so you get the best of both worlds. – amonroejj Apr 20 '23 at 18:42
3

Thumb rule:

The integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable

See Eric Lippert's article here.

In the GetHashCode you should use only readonly or immutable fields; otherwise, the object cannot be used in a Dictionary, Hashtable and other hash-based collections.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • thanks I see now! Can you please check my edits for the second part of my question? – Mauro Ganswer Apr 29 '16 at 12:38
  • Conventionally, .NET collections do not return true for `Equals` if they contain the same values. You should let the original `GetHashCode` and `Equals` to reflect reference equality. However, you can implement the `IEnumerable` interface and use the `SequenceEqual` extension method, or you can implement a custom equality check or overload the `==` operator, etc. – György Kőszeg Apr 29 '16 at 12:46
0

Instead of initialising hashcode to 17 you should initialise it with base.GetHashCode(). int hashCode = base.GetHashCode(); // not 17

Mukesh Adhvaryu
  • 642
  • 5
  • 16