Search code examples
c#.netcontainsiequatable

Comparing Collection of Objects with ContainsExactly


I am trying to compare two collections of objects that have the exact same objects in their respective collections. I wrote an extension method for ContainsExactly to do so.

However, I am running into an issue where it is saying the collections are not the same even though they are. Here is the test code below:

public static bool ContainsExactly<T>(this List<T> set1, List<T> set2)
    {
        if (set1.Count != set2.Count)
            return false;

        //var isEqual = new HashSet<T>(set1).SetEquals(set2); original test just returned isEqual
        var result = set1.Except(set2);            

        return !result.Any(); //still yields both collections in result


    }

So then I have my objects:

public class ReferenceClassObjectTest : IEquatable<ReferenceClassObjectTest>
    {
        public int Id { get; set; }
        public TestObject TestObject { get; set; }


        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;

            return Equals((ReferenceClassObjectTest)obj);
        }

        public bool Equals(ReferenceClassObjectTest other)
        {
            var casted = other as ReferenceClassObjectTest;

            if (casted == null)
                return false;

            return Id == casted.Id && TestObject == casted.TestObject;
        }

        public override int GetHashCode()
        {
            var hash = Id;                   

            if(TestObject != null)
            {
                hash = (hash * 397) ^ TestObject.GetHashCode();
            }
            else
            {
                hash = (hash * 397);
            }

            return hash;

        }
    }

public class TestObject : IEquatable<TestObject>
    {
        public int Id { get; set; }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;

            return Equals((TestObject)obj);
        }

        public bool Equals(TestObject other)
        {
            var casted = other as TestObject;

            if (casted == null)
                return false;

            return Id == casted.Id;
        }

        public override int GetHashCode()
        {
            var hashCode = Id;
            hashCode = (hashCode * 397);
            return hashCode;
        }
    }

And when I run my tests, they still return false:

var set2 = new List<ReferenceClassObjectTest>()
        {
            new ReferenceClassObjectTest
            {
                Id = 1,
                TestObject = new TestObject
                {
                    Id = 2
                }
            },
            new ReferenceClassObjectTest
            {
                Id = 2,
                TestObject = new TestObject
                {
                    Id = 3
                }
            },

        };
        var set3 = new List<ReferenceClassObjectTest>()
        {
            new ReferenceClassObjectTest
            {
                Id = 1,
                TestObject = new TestObject
                {
                    Id = 2
                }
            },
            new ReferenceClassObjectTest
            {
                Id = 2,
                TestObject = new TestObject
                {
                    Id = 3
                }
            },

        };

Assert.IsTrue(set2.ContainsExactly(set3));

Any insights as to why they are not comparing correctly even when overridding GetHashCode()?

I figured the HashSet.SetEquals() would take into account my overrides for GetHashCode() and when I call get hashcode on the two seperate objects in the list, I do get the same hashes:.

set3[1].TestObject.GetHashCode() 1191

set2[1].TestObject.GetHashCode() 1191

set2[0].GetHashCode() 663

set3[0].GetHashCode() 663


Solution

  • Your definition of Equals for ReferenceClassObjectTest uses == instead of calling Equals for TestObject, so you are comparing the reference identity of the TestObjects.

    Change it to be:

    return Id == other.Id && TestObject.Equals(other.TestObject);
    

    Alternatively, if you consider TestObject an immutable object (since it has a public int field, I am thinking not) you should implement operator== to call Equals.