ComponentBase(Of T) Class for NHibernate Components

December 10, 2010 | NHibernate

This post aims to provide a way to implement a base class for NHibernate components also known as Value Objects in Domain-driven design.

In my previous post I discussed about the case where you want to map a component with NHibernate and introduced the ComponentBase(Of T) class. However, to make it straightforward that you need to override Equals (you also need to override GetHashCode) in your derived classes, I modified the ComponentBase(Of T) class to implement the IEquatable(Of T) interface. Furthermore, since NHibernate works only with reference types (that is, a class) I also constrained it to accept only reference types.

Here is the Component(Of T) class:

using System;

public abstract class ComponentBase<T> 
    : IEquatable<T> where T : class 
{
    /// <summary>
    /// Indicates whether the current object is equal to another
    /// object of the same type.
    /// </summary>
    /// <param name="other">An object to compare with this object.
    /// </param>
    /// <returns>true if the current object is equal to the other
    /// parameter; otherwise, false.</returns>
    public abstract bool Equals(T other);

    /// <summary>
    /// Serves as a hash function for a particular type,
    /// suitable for use in hashing
    /// algorithms and data structures such as a hash table.
    /// </summary>
    /// <returns>
    /// A hash code for this instance of the type.
    /// </returns>
    public abstract int GetHashCodeForType();

    /// <summary>
    /// Determines whether the specified <see cref="System.Object"/>
    /// is equal to this instance.
    /// </summary>
    /// <param name="obj">The <see cref="System.Object"/> to
    /// compare with this instance.
    /// </param>
    /// <returns>
    ///     <c>true</c> if the specified <see cref="System.Object"/>
    ///     is equal to this instance;
    ///     otherwise, <c>false</c>.
    /// </returns>
    public sealed override bool Equals(object obj)
    {
        // The given object to compare to can't be null.
        if (obj == null) { return false; }

        // If objects are different types, they can't be equal.
        if (this.GetType() != obj.GetType()) { return false; }

        return Equals(obj as T);
    }

    /// <summary>
    /// Returns a hash code for this instance.
    /// </summary>
    /// <returns>
    /// A hash code for this instance, suitable for
    /// use in hashing algorithms and data structures
    /// like a hash table.
    /// </returns>
    public sealed override int GetHashCode()
    {
        return GetHashCodeForType();
    }
}

Below are some test cases:

using System;
using Xunit;

public sealed class ComponentBaseTest
{
    private sealed class MockType 
         : ComponentBase<MockType> { /* ... */ }

    [Fact]
    public void TheSameInstanceHasTheSameHashCode()
    {
        MockType mt1 = new MockType(5);
        MockType mt2 = mt1;
        Assert.Equal(mt1.GetHashCode(), mt2.GetHashCode());
    }

    [Fact]
    public void DiffInstancesWithSameCtorParamsHaveTheSameHashCode()
    {
        MockType mt1 = new MockType(5);
        MockType mt3 = new MockType(5);
        Assert.Equal(mt1.GetHashCode(), mt3.GetHashCode());
    }

    [Fact]
    public void TestDiffInstancesWithSameCtorParamsAreEqual()
    {
        MockType mt1 = new MockType(5);
        MockType mt3 = new MockType(5);
        // Objects are equal.
        Assert.True(mt1.Equals(mt3));
        // References are not equal.
        Assert.False(object.ReferenceEquals(mt1, mt3));
    }

    [Fact]
    public void TestDiffInstancesWithDiffCtorParamsAreNotEqual()
    {
        MockType mt1 = new MockType(1);
        MockType mt3 = new MockType(3);
        // Objects are not equal.
        Assert.False(mt1.Equals(mt3));
        // References are not equal.
        Assert.False(object.ReferenceEquals(mt1, mt3));
    }
}

You can use this class in your component collections. If you want to map a Set (that is, an unordered collection of unique entities where duplicates are not allowed) just derive your component types from ComponentBase(Of T). Derived types need to implement the strongly-typed version of Equals and also the GetHashCode methods.