Condividi tramite


Fluent Interface for System.Identity – M, Persistence, & GetHashCode()

As I wrote about last time, leveraging the System.Identity schema with the Oslo Repository introduces some ORM database persistence considerations into the design of the fluent interface. In the last article I introduced how to override the Equals() method inherited from System.Object. In accordance with MSDN guidance, I’ll be writing an overload of the GetHashCode() method inherited from System.Object that, like the Equals() override, addresses the ORM considerations of the fluent interface.

As with the last post, I’ll be leveraging the excellent coverage of the GetHashCode() method in the book “Effective C#”.

In looking at the considerations for an overload of GetHashCode(), the fluent interface code must adhere to the following rules:

  1. If two objects are equal they must generate the same hash code
  2. The hash code is an invariant for an object – meaning that if even if fields on the object are changed, the hash code always remains the same
  3. The hash function should generate a random distribution of integer values – this ensures efficiency (aka read less collisions in a hash table) so it is more desirable than a hard and fast rule

Given that our fluent interface is essentially a runtime representation of the System.Identity schema in the Oslo Repository, the Id field (which is populated by SQL Server when objects are persisted the first time) qualifies as the hash code source for fluent interface objects that are rehydrated from the Oslo Repository. However, the Id field won’t work for objects that haven’t yet been persisted to the Repository.

Ergo, our GetHashCode() implementation will need to handle two primary objects states – persisted and non-persisted.

 

The State of the Hash

Addressing this split state requirement necessitates the use of a member variable, so for the time being I’ll locate this new field in the SystemIdentityBase class so that all the fluent interface classes can inherit it:

    1: public abstract class SystemIdentityBase
    2: {
    3:     protected Int64? Id { get; private set; }
    4:     protected int Folder { get; private set; }
    5:     private int? BaseHashCode { get; set; }
    6:  
    7:     // Rest of code removed for brevity
    8: }

 

As the above code snippet illustrates, I’ll use a nullable integer field to store the hash code for fluent interface objects that haven’t yet been assigned an Id value by the Oslo Repository (SQL Server).

I’ll continue to leverage the SystemIdentityBase class for the GetHashCode() implementation:

    1: public override int GetHashCode ()
    2: {
    3:     if ( BaseHashCode.HasValue )
    4:     {
    5:         return BaseHashCode.Value;
    6:     }
    7: }

 

Simple enough – if it isn’t null, use the BaseHashCode for the return value of the GetHashCode() method. Putting this first in the method implementation ensures compliance with rule #2 above.

The next step is to address the situation where a fluent interface object has been loaded from the Oslo Repository. In this case I’ll use the hash code of the Id field as the hash code for the object:

    1: if (Id.HasValue)
    2: {
    3:     return Id.GetHashCode();
    4: }

 

As I know that the Oslo Repository masters the Id field (not to mention that fact that it’s marked private and can’t be altered), the usage of the Id field’s hash code addresses rules #1 and #2 above.

Lastly, I need to handle the situation where a fluent interface object is being stuck in a hash table, but hasn’t been persisted yet. To keep things simple (and correct), I’ll punt in this case to the System.Object implementation of GetHashCode(). The following snippet shows the complete method implementation:

    1: public override int GetHashCode ()
    2: {
    3:     if ( BaseHashCode.HasValue )
    4:     {
    5:         return BaseHashCode.Value;
    6:     }
    7:  
    8:     if (Id.HasValue)
    9:     {
   10:         return Id.GetHashCode();
   11:     }
   12:     else
   13:     {
   14:         BaseHashCode = base.GetHashCode();
   15:  
   16:         return BaseHashCode.Value;
   17:     }
   18: }

 

Now I’ve got a reusable implementation of GetHashCode() that all the fluent interface classes will get for “free”.

 

Houston, We Have a Problem

With this implementation of GetHashCode() sitting in the SystemIdentityBase class I’ve got a bit of a problem. In the last post I placed the Equals() override in the Kind class. Looks like some quick refactoring is in order.

In looking at the Equals() code, most of it is easily transferrable to the SystemIdentityBase class. After some quick VS magic I have the following method added to SystemIdentityBase:

    1: public override bool Equals ( object obj )
    2: {
    3:     if ( obj == null )
    4:     {
    5:         return false;
    6:     }
    7:  
    8:     if ( ReferenceEquals ( this, obj ) )
    9:     {
   10:         return true;
   11:     }
   12:  
   13:     if ( this.GetType () != obj.GetType () )
   14:     {
   15:         return false;
   16:     }
   17:  
   18:     Kind otherKind = obj as Kind;
   19:  
   20:     if ( Id.HasValue && otherKind.Id.HasValue && ( Id.Value == otherKind.Id.Value ) )
   21:     {
   22:         return true;
   23:     }
   24:  
   25:     return Name == otherKind.Name &&
   26:            Keywords == otherKind.Keywords &&
   27:            Owner.Equals ( otherKind.Owner );
   28: }

 

The problem arises on lines 18-27 in the snippet above – this is code specific to the Kind class. What I need is for SystemIdentityBase to provide an Equals() implementation that could be overridden on a class-by-class basis. Some minor tweaks and a quick virtual method should do the trick:

    1: public override bool Equals ( object obj )
    2: {
    3:     if ( obj == null )
    4:     {
    5:         return false;
    6:     }
    7:  
    8:     if ( ReferenceEquals ( this, obj ) )
    9:     {
   10:         return true;
   11:     }
   12:  
   13:     if ( this.GetType () != obj.GetType () )
   14:     {
   15:         return false;
   16:     }
   17:  
   18:     SystemIdentityBase otherObject = obj as SystemIdentityBase;
   19:  
   20:     if ( Id.HasValue && otherObject.Id.HasValue && ( Id.Value == otherObject.Id.Value ) )
   21:     {
   22:         return true;
   23:     }
   24:  
   25:     return CompareObjectMembers ( otherSystemIdentityBase );
   26: }
   27:  
   28: protected virtual bool CompareObjectMembers ( SystemIdentityBase otherObject )
   29: {
   30:     return false;
   31: }

 

I like it. The fluent interface is taking on a “frameworky” vibe that just gives an OO bigot like me a deep sense of goodness (it’s sad, I know). All fluent interface classes will now inherit ORM-aware implementations of Equals() and GetHashCode() and have the option of overriding the Equals() functionality if needed.

As a last step I’ll remove the Equals() implementation out of the Kind class and add the following override to the Kind class:

    1: protected override bool CompareObjectMembers ( SystemIdentityBase otherObject )
    2: {
    3:     Kind otherKind = otherObject as Kind;
    4:  
    5:     return Name == otherKind.Name &&
    6:            Keywords == otherKind.Keywords &&
    7:            Owner.Equals ( otherKind.Owner );
    8: }

 

Houston, We’re Not Quite Done

Some readers will unlikely notice that this last code snippet breaks rule #1 listed above. Not a problem, I’ll refactor a little OO goodness into the GetHashCode() method to address the problem:

    1: public override int GetHashCode ()
    2: {
    3:     if ( BaseHashCode.HasValue )
    4:     {
    5:         return BaseHashCode.Value;
    6:     }
    7:  
    8:     if ( Id.HasValue )
    9:     {
   10:         return Id.GetHashCode ();
   11:     }
   12:     else
   13:     {
   14:         BaseHashCode = GetBaseHashCode ();
   15:  
   16:         return BaseHashCode.Value;
   17:     }
   18: }
   19:  
   20: protected virtual int GetBaseHashCode ()
   21: {
   22:     return base.GetHashCode ();
   23: }

 

Excellent. As with the Equals() method there’s a default implementation that will work with a large number of the fluent interface classes, but I’ve provided a nice extensibility point for classes that need different behavior.

To wrap up this post I’ll add a override to the Kind class for the GetHashCode() functionality that it needs:

    1: protected override int GetBaseHashCode ()
    2: {
    3:     int hash = 23;
    4:  
    5:     hash = ( ( hash << 5 ) * 37 ) + ( Name == null ? 0 : Name.GetHashCode () );
    6:     hash = ( ( hash << 5 ) * 37 ) + ( Keywords == null ? 0 : Keywords.GetHashCode () );
    7:     hash = ( ( hash << 5 ) * 37 ) + ( Owner == null ? 0 : Owner.GetHashCode () );
    8:  
    9:     return hash;
   10: }

 

The algorithm above I gleaned from an excellent post that interested folks can read here. Please note that I do not profess to be a hash code algorithms expert, however the code snippet above doesn’t seem to far out of whack from a number of articles/posts I’ve read on the subject.

 

Next Time

For my next post in the series I’ll take a look at an Expression Builder for the Kind class.

Until next time, thanx for reading!