Generic Value Object Equality


This post was originally published here.

I read a post from Oren the other day where he posted some code for a generic Entity base type that implemented the correct equality logic.  I realized that I’ve needed a generic base type for Value Objects as well.

Value Object Requirements

In the Domain Driven Design space, a Value Object:

  • Has no concept of an identity
    • Two different instances of a Value Object with the same values are considered equal
    • Describes a characteristic of another thing
      • Is immutable

      Unfortunately, in nearly all cases I’ve run in to, we can’t use Value Types in .NET to represent Value Objects.  Value Types (struct) have some size limitations (~16 bytes or less), which we run into pretty quickly.  Instead, we can create a Reference Type (class) with Value Type semantics, similar to the .NET String type.  The String type is a Reference Type, but exhibits Value Type semantics, since it is immutable.  For a Reference Type to exhibit Value Type semantics, it must:

    • Be immutable

      • Override the Equals method, to implement equality instead of identity, which is the default

      Additionally, Framework Design Guidelines has some additional requirements I must meet:

    • Provide a reflexive, transitive, and symmetric implementation of Equals

      • Override GetHashCode
        • Implement IEquatable

          • Override the equality operators

        Generic Implementation

        What I wanted was a base class that would give me all of the Framework Design Guidelines requirements as well as the Domain Driven Design requirements, without any additional logic from concrete types.  Here’s what I ended up with:

        public abstract class ValueObject<T> : IEquatable<T>

    where T : ValueObject<T>
{
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        T other = obj as T;

        return Equals(other);
    }

    public override int GetHashCode()
    {
        IEnumerable<FieldInfo> fields = GetFields();

        int startValue = 17;
        int multiplier = 59;

        int hashCode = startValue;

        foreach (FieldInfo field in fields)
        {
            object value = field.GetValue(this);

            if (value != null)
                hashCode = hashCode * multiplier + value.GetHashCode();
        }

        return hashCode;
    }

    public virtual bool Equals(T other)
    {
        if (other == null)
            return false;

        Type t = GetType();
        Type otherType = other.GetType();

        if (t != otherType)
            return false;

        FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        foreach (FieldInfo field in fields)
        {
            object value1 = field.GetValue(other);
            object value2 = field.GetValue(this);

            if (value1 == null)
            {
                if (value2 != null)
                    return false;
            }
            else if (! value1.Equals(value2))
                return false;
        }

        return true;
    }

    private IEnumerable<FieldInfo> GetFields()
    {
        Type t = GetType();

        List<FieldInfo> fields = new List<FieldInfo>();

        while (t != typeof(object))
        {
            fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

            t = t.BaseType;
        }

        return fields;
    }

    public static bool operator ==(ValueObject<T> x, ValueObject<T> y)
    {
        return x.Equals(y);
    }

    public static bool operator !=(ValueObject<T> x, ValueObject<T> y)
    {
        return ! (x == y);
    }
}

                    I borrowed a little bit from the .NET [ValueType](http://msdn2.microsoft.com/en-us/library/system.valuetype.aspx) base class for the implementation of Equals.&nbsp; The ValueObject<T> type uses reflection to access and compare all internal fields for Equals, as well as for GetHashCode.&nbsp; All implementers will need to do is to ensure that their concrete type is immutable, and they&#8217;re done.
                    
                    I could probably optimize the reflection calls and cache them, but this implementation is mainly for reference anyway.
                    
                    ### The Tests
                    
                    Just for completeness, I&#8217;ll include the set of [NUnit](http://www.nunit.org/) tests I used to write this class up.&nbsp; I think the tests describe the intended behavior well enough.
                    
                    <div class="CodeFormatContainer">
                      <pre>[TestFixture]<br />

public class ValueObjectTests
{
    private class Address : ValueObject<Address>
    {
        private readonly string _address1;
        private readonly string _city;
        private readonly string _state;

        public Address(string address1, string city, string state)
        {
            _address1 = address1;
            _city = city;
            _state = state;
        }

        public string Address1
        {
            get { return _address1; }
        }

        public string City
        {
            get { return _city; }
        }

        public string State
        {
            get { return _state; }
        }
    }

    private class ExpandedAddress : Address
    {
        private readonly string _address2;

        public ExpandedAddress(string address1, string address2, string city, string state)
            : base(address1, city, state)
        {
            _address2 = address2;
        }

        public string Address2
        {
            get { return _address2; }
        }

    }

    [Test]
    public void AddressEqualsWorksWithIdenticalAddresses()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address1”, “Austin”, “TX”);

        Assert.IsTrue(address.Equals(address2));
    }

    [Test]
    public void AddressEqualsWorksWithNonIdenticalAddresses()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address2”, “Austin”, “TX”);

        Assert.IsFalse(address.Equals(address2));
    }

    [Test]
    public void AddressEqualsWorksWithNulls()
    {
        Address address = new Address(null, “Austin”, “TX”);
        Address address2 = new Address(“Address2”, “Austin”, “TX”);

        Assert.IsFalse(address.Equals(address2));
    }

    [Test]
    public void AddressEqualsWorksWithNullsOnOtherObject()
    {
        Address address = new Address(“Address2”, “Austin”, “TX”);
        Address address2 = new Address(“Address2”, null, “TX”);

        Assert.IsFalse(address.Equals(address2));
    }
    
    [Test]
    public void AddressEqualsIsReflexive()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);

        Assert.IsTrue(address.Equals(address));
    }

    [Test]
    public void AddressEqualsIsSymmetric()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address2”, “Austin”, “TX”);

        Assert.IsFalse(address.Equals(address2));
        Assert.IsFalse(address2.Equals(address));
    }

    [Test]
    public void AddressEqualsIsTransitive()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address1”, “Austin”, “TX”);
        Address address3 = new Address(“Address1”, “Austin”, “TX”);

        Assert.IsTrue(address.Equals(address2));
        Assert.IsTrue(address2.Equals(address3));
        Assert.IsTrue(address.Equals(address3));
    }

    [Test]
    public void AddressOperatorsWork()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address1”, “Austin”, “TX”);
        Address address3 = new Address(“Address2”, “Austin”, “TX”);

        Assert.IsTrue(address == address2);
        Assert.IsTrue(address2 != address3);
    }

    [Test]
    public void DerivedTypesBehaveCorrectly()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        ExpandedAddress address2 = new ExpandedAddress(“Address1”, “Apt 123”, “Austin”, “TX”);

        Assert.IsFalse(address.Equals(address2));
        Assert.IsFalse(address == address2);
    }

    [Test]
    public void EqualValueObjectsHaveSameHashCode()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address1”, “Austin”, “TX”);

        Assert.AreEqual(address.GetHashCode(), address2.GetHashCode());
    }

    [Test]
    public void TransposedValuesGiveDifferentHashCodes()
    {
        Address address = new Address(null, “Austin”, “TX”);
        Address address2 = new Address(“TX”, “Austin”, null);

        Assert.AreNotEqual(address.GetHashCode(), address2.GetHashCode());
    }

    [Test]
    public void UnequalValueObjectsHaveDifferentHashCodes()
    {
        Address address = new Address(“Address1”, “Austin”, “TX”);
        Address address2 = new Address(“Address2”, “Austin”, “TX”);

        Assert.AreNotEqual(address.GetHashCode(), address2.GetHashCode());
    }

    [Test]
    public void TransposedValuesOfFieldNamesGivesDifferentHashCodes()
    {
        Address address = new Address("_city", null, null);
        Address address2 = new Address(null, "_address1", null);

        Assert.AreNotEqual(address.GetHashCode(), address2.GetHashCode());
    }

    [Test]
    public void DerivedTypesHashCodesBehaveCorrectly()
    {
        ExpandedAddress address = new ExpandedAddress(“Address99999”, “Apt 123”, “New Orleans”, “LA”);
        ExpandedAddress address2 = new ExpandedAddress(“Address1”, “Apt 123”, “Austin”, “TX”);

        Assert.AreNotEqual(address.GetHashCode(), address2.GetHashCode());
    }

}

                    #### **UPDATE: 6/27/07**
                    
                      * Changed ValueObject<T> to implement IEquatable<T> instead of IEquatable<ValueObject<T>> 
                          * Equals reflects derived type instead of base type, since C# generics are not covariant (or contravariant), IEquatable<ValueObject<T>> != IEquatable<T>
                          * Changed GetHashCode algorithm to use&nbsp;a calculated hash to cover additional test cases 
                              * Gather parent type field values 
                                  * Fix transposed value bug</ul> 
                                  * Fixed NullReferenceException bug when &#8220;other&#8221; is null for IEquatable<T>.Equals 
                                      * Added tests to cover bugs and fixes</ul>
← Disable annoying computer beep
ReSharper plugin for cyclomatic complexity →