What's the best strategy for Equals and GetHashCode?

后端 未结 7 1977
你的背包
你的背包 2020-11-28 11:25

I\'m working with a domain model and was thinking about the various ways that we have to implement these two methods in .NET. What is your preferred strategy?

This i

7条回答
  •  野性不改
    2020-11-28 12:24

    I want to look at some specific scenarios based on the answers above, and my own experiences.

    A rule of thumb is, two instances with different hash codes should always be not equal, but if the have the same hash code they might or might not be equals. GetHashCode() is used to differentiate between instances quickly, and Equals() is used to verify equality (whatever that means to you).

    Also a lot of built-in mechanisms look for an implementation of IEquatable so it is a good idea to declare an override of Equals(MyClass) that actually does the checking.

    Class with unique ID

    Consider a class with a unique ID. Then the equals operation would just check the id. The same with the hash, which solely relies on the id.

    public class IdClass : IEquatable
    {
        public int ID { get; } // Assume unique
        public string Name { get; }
    
    
        #region IEquatable Members
        /// 
        /// Equality overrides from 
        /// 
        /// The object to compare this with
        /// False if object is a different type, otherwise it calls Equals(IdClass)
        public override bool Equals(object obj)
        {
            if (obj is IdClass other)
            {
                return Equals(other);
            }
            return false;
        }
    
        /// 
        /// Checks for equality among  classes
        /// 
        /// The other  to compare it to
        /// True if equal
        public virtual bool Equals(IdClass other)
        {
            if (other == null) return false;
            return ID.Equals(other.ID);
        }
    
        /// 
        /// Calculates the hash code for the 
        /// 
        /// The int hash value
        public override int GetHashCode() => ID.GetHashCode();
    
        #endregion
    
    }
    

    Class with properties

    This case is similar to the above, but the comparisons depend on two or more properties, and the need to be combined asymmetrically in the hash code. This will become more evident in the next scenario, but the idea is if one property has hash A and the other property hash B, the result should difference from the case where first property has hash B and the other hash A.

    public class RefClass : IEquatable
    {
        public string Name { get; }
        public int Age { get; }
    
    
        #region IEquatable Members
        /// 
        /// Equality overrides from 
        /// 
        /// The object to compare this with
        /// False if object is a different type, otherwise it calls Equals(RefClass)
        public override bool Equals(object obj)
        {
            if (obj is RefClass other)
            {
                return Equals(other);
            }
            return false;
        }
    
        /// 
        /// Checks for equality among  classes
        /// 
        /// The other  to compare it to
        /// True if equal
        public virtual bool Equals(RefClass other)
        {
            if (other == null) { return false; }
            return Name.Equals(other.Name)
                && Age.Equals(other.Age);
        }
    
        /// 
        /// Calculates the hash code for the 
        /// 
        /// The int hash value
        public override int GetHashCode()
        {
            unchecked
            {
                int hc = -1817952719;
                hc = (-1521134295) * hc + Name.GetHashCode();
                hc = (-1521134295) * hc + Age.GetHashCode();
                return hc;
            }
        }
    
        #endregion
    
    }
    

    Value based class (structure)

    This is almost identical to the case above, except being a value type (struct declaration) requires also re-definition of == and != to call equals.

    public struct ValClass : IEquatable
    {
        public int X { get; }
        public int Y { get; }
    
        #region IEquatable Members
        /// 
        /// Equality overrides from 
        /// 
        /// The object to compare this with
        /// False if object is a different type, otherwise it calls Equals(ValClass)
        public override bool Equals(object obj)
        {
            if (obj is ValClass other)
            {
                return Equals(other);
            }
            return false;
        }
    
        public static bool operator ==(ValClass target, ValClass other) { return target.Equals(other); }
        public static bool operator !=(ValClass target, ValClass other) { return !(target == other); }
    
    
        /// 
        /// Checks for equality among  classes
        /// 
        /// The other  to compare it to
        /// True if equal
        public bool Equals(ValClass other)
        {
            return X == other.X && Y == other.Y;
        }
    
        /// 
        /// Calculates the hash code for the 
        /// 
        /// The int hash value
        public override int GetHashCode()
        {
            unchecked
            {
                int hc = -1817952719;
                hc = (-1521134295) * hc + X.GetHashCode();
                hc = (-1521134295) * hc + Y.GetHashCode();
                return hc;
            }
        }
    
        #endregion
    
    }
    

    Note that struct should be immutable, and it is a good idea to add the readonly keyword in the declaration

    public readonly struct ValClass : IEquatable
    {
    } 
    

提交回复
热议问题