0

I have a form responsible of creating (and saving) new Patients. On this form I am using an ErrorProvider to show error icons on invalid fields (in this case just "LastName"). So, as usual => errorProvider.DataSource = patient;

Everything works fine when my model uses default GetHashCode(). But when I try to override this method using a custom hash code (I want to use this model with ISet collections) the control does not work properly. Now, I understand that custom hash codes should be used just for immutable objects. But the point is, how can I fill the fields of these objects if the ErrorProvider behaviour relays on GetHashCode to work properly? Is it necessary to implement a Dirty mechanism that switches between default hash code (during object initialization) and custom hash?

Code sample:

public class Patient : IDataErrorInfo, INotifyPropertyChanged
{
    public string lastName;

    public virtual string LastName
    {
        get { return lastName; }
        set
        {
            if (lastName == value) return;
            lastName = value;
            NotifyPropertyChanged("LastName");

        }
    }

    #region IDataErrorInfo Members

    string IDataErrorInfo.Error { get { return null; } }

    string IDataErrorInfo.this[string propertyName]
    {
        get { return this.GetValidationError(propertyName); }
    }

    #endregion // IDataErrorInfo Members


    protected string GetValidationError(string propertyName)
    {
            if (ValidatedProperties.IndexOf(propertyName) < 0)
                return null;

            string error = null;

            switch (propertyName)
            {
                case "LastName": 
                    if (LastName == null)
                        error = "null";
                    break;

                default:
                    break;
            }

            return error;
    }

    public virtual event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int result = 17;
            result = 23 * result + ((LastName != null) ? LastName.GetHashCode() : 0);
            return result;
        }
    }
}
Marco
  • 141
  • 1
  • 1
  • 7
  • Have you tried overriding Equals() ? Also, I'd setting a breakpoint inside GetHashCode() and examining the callstack to try to figure out how your control is using it. – sevzas Dec 05 '14 at 22:09
  • Yes, Equals() does not get called at all. It is pretty hard to figure what the ErrorProvider is doing actually. During the ShowDialog it seems that ErrorProvider calls GetHashCode() many times (probably once for every control on my form) and then GetValidationError(string propertyName) for "LastName". The whole validation step is repeated twice (why?). – Marco Dec 06 '14 at 09:58

1 Answers1

0

Each field that is used in GetHashCode function must be immutable. I would not recommend to implement two versions of GetHashCode, because it should be persistent and repeatable. I know one possible way how to solve this problem. If you know that some object will be changed, then you can delete it from set before editing operation and add again into set when all modifications are done. In this case you can skip overriding of GetHashCode and use SortedSet with a specified comparer that implements IComparer interface.

Update

Normally, I would recommend to use HashSet if you don't need sorted set as a result. SortedSet employs binary search tree and it seems that it doesn't use GetHashCode function. SortedSet is a bit slower than HashSet. SortedSet performance is about O(log n), because it has to find a space for inserting element in the sorted set. HashSet takes only O(1).

IComparer helps to find whether two objects are equal (there is no need to call Equals) or tells which of them is less than or greater than other. I wrote a bit of code for testing of SortedSet functionality.

Code

public class Foo
{
    public Foo(string something)
    {
        Something = something;
    }
    public string Something { set; get; }
}

public class BySomething : IComparer<Foo>
{
    private readonly CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
    public int Compare(Foo x, Foo y)
    {
        return _comparer.Compare(x.Something, y.Something);
    }
}

Test

[TestMethod]
public void SortedSetTest()
{
    var first = new Foo("Doe");
    var second = new Foo("Floyd");
    var third = new Foo("Floyd");

    var set = new SortedSet<Foo>(new BySomething());
    set.Add(first);
    set.Add(second);
    set.Add(third);

    Assert.AreEqual(set.Count, 2);
}
Sergii Zhevzhyk
  • 4,074
  • 22
  • 28
  • But if I am using an ORM like nHibernate, what if I have to compare a newly created object with a SortedSet collection filled with database objects? Without a custom GetHashCode() I am not able to detect if the new object is actually copy of an already existing db row. They will have same business key values but different hash codes... so it will be wrongly added to the SortedSet. The IComparer interface is internally used to sort objects but I don't think the collection uses it also to detect copies. That's what Equals()/GetHashCodes() are used for. Am I missing something? – Marco Dec 06 '14 at 10:03