Generic Entity Equality

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);

    }

}

Print | posted on Tuesday, June 05, 2007 6:51 PM

Feedback


Gravatar

# re: Generic Entity Equality 6/6/2007 12:08 AM Luke Breuer

Does the where clause of the generic definition specify infinite recursion, or am I being retarded? Is it possible to specify types to it that satisfy the where condition?


Gravatar

# re: Generic Entity Equality 6/6/2007 12:24 AM Daniel Grunwald

This class will satisfy the constraint:
class B : EqualityAndHashCodeProvider<B, TKey>

For the equals implementation, the base class must know who derived from it, so one can use the type parameter T to tell it.

I think I have also done this somewhere where I needed a mixin.


Gravatar

# re: Generic Entity Equality 6/6/2007 12:29 AM Luke Breuer

Ahh, I was thinking that the recursion would be satisfiable by inheriting the class, but I got lazy. :-( I had just finished solving a really annoying bug and felt like taking a short break from thinking rigorously; oops! I'm going to have to remember this, ahem, design pattern.


Gravatar

# re: Generic Entity Equality 6/6/2007 6:13 PM Ben Scheirman

Oren, isn't there an issue with using the PK as as HashCode source?

Take for example you are creating 2 objects, both transient, but they have different properties. Since they haven't been persisted yet, they have the same key value (0, Guid.Empty, null, whatever) and thus will get the same hash code.

This will place those 2 objects in the same slot in the Cache, which will lead to strange issues.

This is mentioned in Hibernate In Action, so I wanted to hear your input on this...


Gravatar

# re: Generic Entity Equality 6/6/2007 6:54 PM Ayende Rahien

Ben, you are correct, a special case can be made for:

if ( other.Id == default(TKey) && this.Id == default(Tkey) )
return ReferenceEquals(other, this);


Gravatar

# re: Generic Entity Equality 6/6/2007 7:07 PM Ayende Rahien

Updated the post


Gravatar

# re: Generic Entity Equality 6/14/2007 2:10 AM Jesus Garza

Can this go into ActiveRecord?


Gravatar

# re: Generic Entity Equality 6/14/2007 6:57 AM Ayende Rahien

@Jesus,
Not really. AR needs to support more stuff, like multi key entities


Gravatar

# re: Generic Entity Equality 6/26/2007 1:04 AM Jimmy Bogard

This got me all inspired to create a base type for DDD Value Objects.

http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html

Comments have been closed on this topic.