Is there a complete IEquatable implementation reference?

前端 未结 5 530
猫巷女王i
猫巷女王i 2020-11-29 18:44

Many of my questions here on SO concerns IEquatable implementation. I found it being extremely difficult to implement correctly, because there are many hidden bugs in the na

5条回答
  •  一生所求
    2020-11-29 18:55

    I believe getting something as simple as checking objects for equality correct is a bit tricky with .NET's design.

    For Struct

    1) Implement IEquatable. It improves performance noticeably.

    2) Since you're having your own Equals now, override GetHashCode, and to be consistent with various equality checking override object.Equals as well.

    3) Overloading == and != operators need not be religiously done since the compiler will warn if you unintentionally equate a struct with another with a == or !=, but its good to do so to be consistent with Equals methods.

    public struct Entity : IEquatable
    {
        public bool Equals(Entity other)
        {
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Entity))
                return false;
    
            return Equals((Entity)obj);
        }
    
        public static bool operator ==(Entity e1, Entity e2)
        {
            return e1.Equals(e2);
        }
    
        public static bool operator !=(Entity e1, Entity e2)
        {
            return !(e1 == e2);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    For Class

    From MS:

    Most reference types should not overload the equality operator, even if they override Equals.

    To me == feels like value equality, more like a syntactic sugar for Equals method. Writing a == b is much more intuitive than writing a.Equals(b). Rarely we'll need to check reference equality. In abstract levels dealing with logical representations of physical objects this is not something we would need to check. I think having different semantics for == and Equals can actually be confusing. I believe it should have been == for value equality and Equals for reference (or a better name like IsSameAs) equality in the first place. I would love to not take MS guideline seriously here, not just because it isn't natural to me, but also because overloading == doesn't do any major harm. That's unlike not overriding non-generic Equals or GetHashCode which can bite back, because framework doesn't use == anywhere but only if we ourself use it. The only real benefit I gain from not overloading == and != will be the consistency with design of the entire framework over which I have no control of. And that's indeed a big thing, so sadly I will stick to it.

    With reference semantics (mutable objects)

    1) Override Equals and GetHashCode.

    2) Implementing IEquatable isn't a must, but will be nice if you have one.

    public class Entity : IEquatable
    {
        public bool Equals(Entity other)
        {
            if (ReferenceEquals(this, other))
                return true;
    
            if (ReferenceEquals(null, other))
                return false;
    
            //if your below implementation will involve objects of derived classes, then do a 
            //GetType == other.GetType comparison
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as Entity);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    With value semantics (immutable objects)

    This is the tricky part. Can get easily messed up if not taken care..

    1) Override Equals and GetHashCode.

    2) Overload == and != to match Equals. Make sure it works for nulls.

    2) Implementing IEquatable isn't a must, but will be nice if you have one.

    public class Entity : IEquatable
    {
        public bool Equals(Entity other)
        {
            if (ReferenceEquals(this, other))
                return true;
    
            if (ReferenceEquals(null, other))
                return false;
    
            //if your below implementation will involve objects of derived classes, then do a 
            //GetType == other.GetType comparison
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as Entity);
        }
    
        public static bool operator ==(Entity e1, Entity e2)
        {
            if (ReferenceEquals(e1, null))
                return ReferenceEquals(e2, null);
    
            return e1.Equals(e2);
        }
    
        public static bool operator !=(Entity e1, Entity e2)
        {
            return !(e1 == e2);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    Take special care to see how it should fare if your class can be inherited, in such cases you will have to determine if a base class object can be equal to a derived class object. Ideally, if no objects of derived class is used for equality checking, then a base class instance can be equal to a derived class instance and in such cases, there is no need to check Type equality in generic Equals of base class.

    In general take care not to duplicate code. I could have made a generic abstract base class (IEqualizable or so) as a template to allow re-use easier, but sadly in C# that stops me from deriving from additional classes.

提交回复
热议问题