2

I'm trying to write an Equality Comparer for a simple class with 3 fields, like so:

public class NumberClass
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}

My condition for two objects of NumberClass to be equal is if Obj1.A == Obj2.A || Obj1.B == Obj2.B (in other words, OR), Obj1 and Obj2 being instances of NumberClass.

I can easily write the Equals() of my comparer as follows, but I don't know what to do with my GetHashCode() method.

public bool Equals(NumberClass x, NumberClass y)
{
    if (x.A == y.A || x.B == y.B)
        return true;
    else
        return false;
}

public int GetHashCode(NumberClass obj)
{
    return ???
}

If my condition for equality was AND instead of OR, I could write my GetHashCode() as follows, taken from this SO answer.

public int GetHashCode(NumberClass obj)
{
    unchecked
    {
        int hash = 17;
        if (obj != null)
        {
            hash = hash * 23 + obj.A.GetHashCode();
            hash = hash * 23 + obj.B.GetHashCode();
        }
        return hash;
    }
}

But that obviously wouldn't work for OR since only one of A or B being equal is sufficient for my equality condition to be true.

One workaround I could think of is always returning the same value in GetHashCode() which would be sufficient for LINQ operations such as Distinct(), but I feel like there should be another way as that has its own shortcomings.

What's the proper way to handle this situation?

P.S. For testing, imagine my Main() is the following:

static void Main(string[] args)
{
    List<NumberClass> list = new List<NumberClass>();
    list.Add(new NumberClass { A = 1, B = 2, C = 3 });
    list.Add(new NumberClass { A = 1, B = 22, C = 33 });

    var distinct = list.Distinct(new NumberComparer());
    Console.ReadKey();
}

I expect distinct to contain only the first element in the list.

Sach
  • 10,091
  • 8
  • 47
  • 84

1 Answers1

4

There is no solution for your situation. Your objects violate assumptions that are necessary for an equality comparer to work, for example, it assumes that equality is going to be transitive, but that's not true of your implementation of equality.

You simply won't be able to use any hash-based algorithms so long as you have "fuzzy" equality like that.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Can you please explain what you meant by _it assumes that equality is going to be transitive_? – Sach Aug 09 '17 at 21:54
  • 1
    @Sach If A is equal to B, and B is equal to C, then A is equal to C (if equality is transitive). That's not true in your case. – Servy Aug 09 '17 at 21:56
  • Got it, thanks! OK so I guess the only workaround in such a situation is to return the same value in `GetHashCode()`, or simply not use a comparer and do an old school comparison. – Sach Aug 09 '17 at 21:59
  • @Sach Like I said, there's no way you can use an algorithm that uses hashes for equality comparisons. Additionally, the typical definition of "distinct" doesn't even conceptually make sense, given your definition of equality, so not only can you not use the .NET definition, I don't even know how you'd implement it (as in, you need to determine what you *want* to happen, because the typical behavior doesn't make sense given your definition of equality). You'll need to write something from the ground up. – Servy Aug 09 '17 at 22:02
  • I too have never used this sort of equality before. The reason I asked this question is one user asked it in a comment to one of my [SO answers](https://stackoverflow.com/a/45599044/302248), and I was intrigued. – Sach Aug 09 '17 at 22:05
  • @Sach This type of equality comes up most commonly when dealing with floating point numbers; since they have an inherent approximation to their value, when comparing them you often say that they're "equal" if they're within a certain threshold of each other. That's the only practical application that comes to mind. – Servy Aug 09 '17 at 22:07
  • That's good to know. It's been an educational discussion, thanks! – Sach Aug 09 '17 at 22:10