0

I have 2 lists, first on parent objects second child objects. Child object has extra property which i want to compare with a property of the parent class.

here is the example

  public class Parent
{
    public int X { get; set; }
}

public class Child : Parent
{
    public int Y { get; set; }
}

public class ClassXCompare : IEqualityComparer<Parent>
{
    public bool Equals(Parent x, Parent y)
    {
        var child = (Child)y;
        return x.X == child.Y;
    }

    public int GetHashCode(Parent parent)
    {
        int parentXhash = parent.X.GetHashCode();
        // Calculate the hash code for the product. 
        return parentXhash ;
    }
}

and now if i test the following, it always fail

var parentList= new List<Parent>
        {
            new Parent {X = 5},
            new Parent {X = 6}
        };
        var childList= new List<Child>
        {
            new Child {Y = 5},
            new Child {Y = 6}
        };
        var compare = new ClassXCompare();
        var diff = parentList.Except(childList, compare);
        Assert.IsTrue(!diff.Any()); // Fail ???

i think my issue is located in the GetHashCode function

Any idea how to solve this?

(Please ignore the design of the application this is simplified version of the issue)

Maro
  • 2,579
  • 9
  • 42
  • 79
  • Are you sure that `y` will always be a `Child` in `var child = (Child)y;`? And in any case, please, clarify what exactly you mean by "it always fail" (exception, wrong result...). – Eugene Podskal Mar 20 '16 at 15:04
  • Yes y is always a child, there are no exceptions, **var diff = parentList.Except(childList, compare);** should return empty but it doesn't – Maro Mar 20 '16 at 15:07

2 Answers2

2

This is truly horrible design. The need to cast to specific types in the comparer will cause you no end of trouble.

However, the code below passes. Note the different method of casting, the null checks and the order of the lists on the except line.

The problem was that the Child instances do not set X and the order that the Except method passes values into Equals meant that "x" was the Child not "y".

This may "work" but you should serious reconsider your design.

public class Parent
{
    public int X { get; set; }
}

public class Child : Parent
{
    public int Y { get; set; }
}

public class ClassXCompare : IEqualityComparer<Parent>
{
    public bool Equals(Parent x, Parent y)
    {
        var child = y as Child;

        return child != null && x.X == child.Y;
    }

    public int GetHashCode(Parent parent)
    {
        var c = parent as Child;
        if (c == null)
            return parent.X.GetHashCode();
        else
            return c.Y.GetHashCode();
    }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var parentList = new List<Parent>
            {
                    new Parent {X = 5},
                    new Parent {X = 6}
            };
        var childList = new List<Child>
            {
                    new Child {Y = 5},
                    new Child {Y = 6}
            };
        var compare = new ClassXCompare();
        var diff = childList.Except(parentList, compare);
        Assert.IsTrue(!diff.Any()); // Fail ???     
    }
}
AndyPook
  • 2,762
  • 20
  • 23
  • thanks , i do agree about the design, that's why i noted in my question, the thing is i have no authority to change the design. that's why i have to implement this :-( thanks again i will do test – Maro Mar 20 '16 at 15:41
  • fair enough, isn't that always the way :) – AndyPook Mar 21 '16 at 18:03
  • Thanks Andy, i marked your answer, i will re-ask this question because in similar situation i compare 2 doubles but i always get false, but this is another question – Maro Mar 21 '16 at 20:46
  • Doubles (or any fp type) can be difficult due to the precision required. Have a look at the examples on https://msdn.microsoft.com/en-us/library/ya2zha7s(v=vs.110).aspx for a couple of techniques for "equality with tolerance" – AndyPook Mar 22 '16 at 08:57
0

Equals in Set/Dictionary will only be called only if GetHashCode returns the same value for both items - see General advice and guidelines on how to properly override object.GetHashCode(). The simplest way to fix it is to use something like this, where child value is used to calculate HashCode if available:

public class ClassXCompare : IEqualityComparer<Parent>
{
    public bool Equals(Parent x, Parent y)
    {
        var child = (Child)y;
        return x.X == child.Y;
    }

    public int GetHashCode(Parent parent)
    {
        var child = parent as Child;
        return child == null ?
            parent.X : child.Y.
    }
}

Though it is not very extendable if you have to handle multiple child classes.

Not sure that there is an easy way to handle multiple child classes, other than with some custom generic IEqualityComparer and even that will just shorten it a bit.

P.S. Also I am not sure that the second parameter will always be a Child (doubt that standard covers it, though current implementation in this particular usage seems to work in an appropriate way), so you may have to make that IEqualityComparer more lenient to the order of parameters.

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53