ErrorProvider with custom GetHashCode

158 Views Asked by At

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;
        }
    }
}
1

There are 1 best solutions below

1
On

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);
}