0

Is it possible to perform union/except on Lists of Objects where the instance of objects are not necessarily the same but they are functionally equivalent?

What I mean is if I have a class like this,

Class A
{
    String a;
    int b;
    double c;
}

And I had the following Lists:

A foo = new A() {"a",2,3.4}    
A bar = new A() {"a",2,3.4}

List<A> firstList = new List<A>() { foo }
List<A> secondList = new List<A>() { bar }

How can I perform firstList.Except/Union on secondList if firstList and secondList had completely different object instances but the fields/properties of the objects are exactly the same?

TtT23
  • 6,876
  • 34
  • 103
  • 174
  • 1
    How about override the `object.Equals()` method? Then all of the Linq methods that check for equality would use your method, which should check equality based on fields. – feralin Jul 03 '13 at 18:14
  • 1
    And if you override the `Equals` method you should also override the `GetHashCode` method. – Servy Jul 03 '13 at 18:14
  • Actually, I'll add it as an answer. – feralin Jul 03 '13 at 18:15

4 Answers4

1

You need to overload the Equals method of your class.

Right now, the way that it checks for equality is by checking the reference. There's a way to fix that, by overriding the Equals method:

class A
{
    string a;
    int b;
    double c;
    public override bool Equals(object obj)
    {
        A aobj = obj as A;
        if (aobj == null) return false;
        return a == aobj.a && b == aobj.b && c == aobj.c;
    }
}

However, for these functions to perform at their best, you also need to override the GetHashCode method, too. Like this:

class A
{
    string a;
    int b;
    double c;
    public override bool Equals(object obj)
    {
        return a == obj.a && b == obj.b && c == obj.c;
    }
    public override int GetHashCode()
    {
        unchecked { return 17 * (a ?? "").GetHashCode() * b * c.GetHashCode(); }
    }
}
It'sNotALie.
  • 22,289
  • 12
  • 68
  • 103
1

Simply override the object.Equals method to tell the world when to treat your objects as equal. Your class A should look something like this:

class A
{
    string a;
    int b;
    double c;

    public override bool Equals(object obj)
    {
        if (!(obj is A)) return obj.Equals(this); // defer to other object
        A other = (A)obj;
        return a == other.a && b == other.b && c == other.c; // check field equality
    }
    public override int GetHashCode()
    {
        int hc = 13;
        hc += a.GetHashCode() * 27;
        hc += b.GetHashCode() * 27;
        hc += c.GetHashCode() * 27;
    }
}
feralin
  • 3,268
  • 3
  • 21
  • 37
  • Just FYI: `int.GetHashCode` is useless. It's `return this;` – It'sNotALie. Jul 03 '13 at 18:24
  • @newStackExchangeInstance meh, doesn't really matter. It's also implementation dependent, so might as well use the `GetHashCode` method. – feralin Jul 03 '13 at 18:25
  • @newStackExchangeInstance you shouldn't depend on that specific implementation. It doesn't really matter, however, as I already stated. Edit it if you want, I don't care. – feralin Jul 03 '13 at 18:27
0

Adding little more to previous answers. Overriding Equals will require overriding == and !=

public class A
{
    String a;
    int b;
    double c;

    public override bool Equals(object obj)
    {   
        if (object.ReferenceEquals(null, obj))
        {
            return false;
        }

        if (object.ReferenceEquals(this, obj))
        {
            return true;
        }

        if (obj.GetType() != typeof(A))
        {
            return false;
        }

        var other = obj as A;
        return string.Equals(this.a, other.a) && this.b == other.b && this.c == other.b;
    }

    public override int GetHashCode()
    {
        if (string.IsNullOrEmpty(a))
        {
            return this.b.GetHashCode() ^ this.c.GetHashCode();
        }
        return this.a.GetHashCode() ^ this.b.GetHashCode() ^ this.c.GetHashCode();
    }

    public static bool operator ==(A left, A right)
    {
        if (object.ReferenceEquals(left, right))
        {
            return true;
        }

        if (object.ReferenceEquals(null, left))
        {
            return false;
        }

        if (object.ReferenceEquals(null, right))
        {
            return false;
        }

        return left.Equals(right);
    }

    public static bool operator !=(A left, A right)
    {
        return !(left == right);
    }
}
loopedcode
  • 4,863
  • 1
  • 21
  • 21
0

You can use LINQ to Objects to create intersections and unions on lists - and there is no need to override Equals and GetHashCode. Use the Enumerable.Intersect and Enumerable.Except methods:

public class A
{
    public String a;
    public int b;
    public double c;
}
A foo = new A() {a = "a", b = 2, c = 3.4};
A bar = new A() {a = "a", b = 2, c = 3.4};

List<A> firstList = new List<A>() { foo };
List<A> secondList = new List<A>() { bar };

firstList.Except(secondList);
firstList.Intersect(secondList);

The output in this case is:

same entries: 
>> IEnumerable<A> (1 item) 4  
>> a b c 
>> a 2 3,4 

You can make combinations firstList.Method(secondsList) and vice versa. You could event write a custom Comparer - IEqualityComparer in order to compare complex object types.

keenthinker
  • 7,645
  • 2
  • 35
  • 45