Ari Pub
Ari Pub

Reputation: 31

How to Implement IEquatable with Different Equality Checks

I have a MyCustomSet class with IEquatable implemented as shown below.

This works fantastic when I want to check equality for all three sets (SetA*, SetB*, and SetC*). But requirements dictate that I also need the ability to check equality for only SetA*, SetB*, or SetC*, or combination thereof (SetA* and SetB*, SetA* and SetC*, or SetB* and SetC*) and ignore checking equality for any other Set that is not required in the check.

Currently, I'm using foreach and LINQ to iterate through the Sets to perform partial equality checks, and that works, but that doesn't seem very efficient for large datasets.

Maybe the answer is looking at me straight in the face, but I don't see it because I don't have any idea how implement IEquatable that can handle different equality checks.

Would someone assist me with some suggestions or directions as to how this may be implemented? An example would be even more appreciated.

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 439) + h);
    }
}

public class MyCustomSetModel
{
    public sealed class MyCustomSet : IEquatable<MyCustomSet>
    {
        public string ItemName { get; set; }
        public string ItemType { get; set; }

        public double SetA1 { get; set; }
        public double SetA2 { get; set; }
        public double SetA3 { get; set; }

        public double SetB1 { get; set; }
        public double SetB2 { get; set; }
        public double SetB3 { get; set; }

        public double SetC1 { get; set; }
        public double SetC2 { get; set; }
        public double SetC3 { get; set; }

        public bool Equals(MyCustomSet other)
        {
            if (ReferenceEquals(other, null))
            {
                return false;
            }

            if (ReferenceEquals(other, this))
            {
                return true;
            }

            return
                (
                (this.ItemName == other.ItemName) &&
                (this.ItemType == other.ItemType) &&

                (this.SetA1 == other.SetA1) &&
                (this.SetA2 == other.SetA2) &&
                (this.SetA3 == other.SetA3) &&

                (this.SetB1 == other.SetB1) &&
                (this.SetB2 == other.SetB2) &&
                (this.SetB3 == other.SetB3) &&

                (this.SetC1 == other.SetC1) &&
                (this.SetC2 == other.SetC2) &&
                (this.SetC3 == other.SetC3)
                );
        }

        public override bool Equals(object obj) => Equals(obj as MyCustomSet);

        public override int GetHashCode()
        {
            unchecked
            {
                return HashCode.Start
                    .Hash(ItemName)
                    .Hash(ItemType)

                    .Hash(SetA1)
                    .Hash(SetA2)
                    .Hash(SetA3)

                    .Hash(SetB1)
                    .Hash(SetB2)
                    .Hash(SetB3)

                    .Hash(SetC1)
                    .Hash(SetC2)
                    .Hash(SetC3);
            }
        }
    }
}

* Update: *

Thanks to Matthew Watson who pointed me in the right direction. So I implemented a custom comparer as follows. It looks like it is working, but if anyone sees potential problems or room for better implementation, please feel free to comment.

        public sealed class MyCustomSetComparer : IEqualityComparer<MyCustomSet>
        {
            private bool _compareSetA;
            private bool _compareSetB;
            private bool _compareSetC;

            public MyCustomSetComparer(bool compareSetA = true, bool compareSetB = true, bool compareSetC = true)
            {
                _compareSetA = compareSetA;
                _compareSetB = compareSetB;
                _compareSetC = compareSetC;
            }

            public bool Equals(MyCustomSet x, MyCustomSet y)
            {
                if (Object.ReferenceEquals(x, y))
                {
                    return true;
                }

                if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                {
                    return false;
                }

                bool result =
                    (x.ItemName == y.ItemName) &&
                    (x.ItemType == y.ItemType);

                if (_compareSetA)
                {
                    result = result &&
                        (x.SetA1 == y.SetA1) &&
                        (x.SetA2 == y.SetA2) &&
                        (x.SetA3 == y.SetA3);
                }

                if (_compareSetB)
                {
                    result = result &&
                        (x.SetB1 == y.SetB1) &&
                        (x.SetB2 == y.SetB2) &&
                        (x.SetB3 == y.SetB3);
                }

                if (_compareSetC)
                {
                    result = result &&
                        (x.SetC1 == y.SetC1) &&
                        (x.SetC2 == y.SetC2) &&
                        (x.SetC3 == y.SetC3);
                }

                return result;
            }

            public int GetHashCode(MyCustomSet item)
            {
                if (Object.ReferenceEquals(item, null))
                {
                    return 0;
                }

                int hash = HashCode.Start
                    .Hash(item.ItemName)
                    .Hash(item.ItemType);

                if (_compareSetA)
                {
                    hash = hash.Hash(item.SetA1)
                        .Hash(item.SetA2)
                        .Hash(item.SetA3);
                }

                if (_compareSetB)
                {
                    hash = hash.Hash(item.SetB1)
                        .Hash(item.SetB2)
                        .Hash(item.SetB3);
                }

                if (_compareSetC)
                {
                    hash = hash.Hash(item.SetC1)
                        .Hash(item.SetC2)
                        .Hash(item.SetC3);
                }

                unchecked
                {
                    return hash;
                }
            }
        }

Upvotes: 1

Views: 980

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1500835

As per Matthew Watson's comment, this is a perfect time to use IEqualityComparer<T> rather than IEquatable<T>. I would suggest only implementing IEquatable<T> when there's a single obvious, natural definition of equality for a type. When there are multiple options for equality and no single option is more reasonable than the others, implement IEqualityComparer<T> - either in several different implementation types, or in a single implementation type that is parameterized in terms of creation.

Upvotes: 3

Related Questions