Generic Entity Equality

time to read 21 min | 4008 words

Just got bit by reference equality vs. value equality again. Since this is the third time that I am writing this base class, I decided to put it in the blog for future reference (I wrote it first and forgot to overload the == / != operators).

By the way, this is the perfect example of a Mixin.

Updated: Ben Scheirman reminded me that I need to handle trasient objects as well.

/// <summary>

/// This is a trivial class that is used to make sure that Equals and GetHashCode

/// are properly overloaded with the correct semantics. This is exteremely important

/// if you are going to deal with objects outside the current Unit of Work.

/// </summary>

/// <typeparam name="T"></typeparam>

/// <typeparam name="TKey"></typeparam>

public abstract class EqualityAndHashCodeProvider<T, TKey>

    where T : EqualityAndHashCodeProvider<T, TKey>

{

    private int? oldHashCode;

 

    /// <summary>

    /// Determines whether the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>.

    /// </summary>

    /// <param name="obj">The <see cref="T:System.Object"></see> to compare with the current <see cref="T:System.Object"></see>.</param>

    /// <returns>

    /// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.

    /// </returns>

    public override bool Equals(object obj)

    {

        T other = obj as T;

        if (other == null)

            return false;

        //to handle the case of comparing two new objects

        bool otherIsTransient = Equals(other.Id ,default(TKey));

        bool thisIsTransient = Equals(this.Id, default(TKey));

        if (otherIsTransient && thisIsTransient)

            return ReferenceEquals(other, this);

        return other.Id.Equals(Id);

    }

 

    /// <summary>

    /// Serves as a hash function for a particular type. <see cref="M:System.Object.GetHashCode"></see> is suitable for use in hashing algorithms and data structures like a hash table.

    /// </summary>

    /// <returns>

    /// A hash code for the current <see cref="T:System.Object"></see>.

    /// </returns>

    public override int GetHashCode()

    {

        //This is done se we won't change the hash code

        if (oldHashCode.HasValue)

            return oldHashCode.Value;

        bool thisIsTransient = Equals(this.Id, default(TKey));

        //When we are transient, we use the base GetHashCode()

        //and remember it, so an instance can't change its hash code.

        if (thisIsTransient)

        {

            oldHashCode = base.GetHashCode();

            return oldHashCode.Value;

        }

        return Id.GetHashCode();

    }

 

    /// <summary>

    /// Get or set the Id of this entity

    /// </summary>

    public abstract TKey Id { get; set; }

 

    /// <summary>

    /// Equality operator so we can have == semantics

    /// </summary>

    public static bool operator ==(EqualityAndHashCodeProvider<T, TKey> x, EqualityAndHashCodeProvider<T, TKey> y)

    {

        return Equals(x, y);

    }

 

    /// <summary>

    /// Inequality operator so we can have != semantics

    /// </summary>

    public static bool operator !=(EqualityAndHashCodeProvider<T, TKey> x, EqualityAndHashCodeProvider<T, TKey> y)

    {

        return !(x == y);

    }

}