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.