Generic Value Object

June 7th, 2009

I just wanted to share my attempt for implementing a generic base class for Value Objects, popularized by Eric Evans and the Domain-Driven Design community. I must say that I got heavily inspired by Jimmy Bogard’s implementation, which got me thinking about such an approach. Contrary to his implementation, I used static reflection instead of dynamic reflection in order to determine which fields to use for equality and string representation.

public abstract class ValueObject<T> : IEquatable<T>
    where T : ValueObject<T>
{
    private List<PropertyInfo> Properties { get; set; }

    protected ValueObject()
    {
        Properties = new List<PropertyInfo>();
    }

    public override Boolean Equals(Object obj)
    {
        if(ReferenceEquals(null, obj)) return false;
        if(obj.GetType() != GetType()) return false;

        return Equals(obj as T);
    }

    public Boolean Equals(T other)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;

        foreach(var property in Properties)
        {
            var oneValue = property.GetValue(this, null);
            var otherValue = property.GetValue(other, null);

            if(null == oneValue || null == otherValue)  return false;
            if(false == oneValue.Equals(otherValue)) return false;
        }

        return true;
    }

    public override Int32 GetHashCode()
    {
        var hashCode = 36;
        foreach(var property in Properties)
        {
            var propertyValue = property.GetValue(this, null);
            if(null == propertyValue)
                continue;

            hashCode = hashCode ^ propertyValue.GetHashCode();
        }

        return hashCode;
    }

    public override String ToString()
    {
        var stringBuilder = new StringBuilder();
        foreach(var property in Properties)
        {
            var propertyValue = property.GetValue(this, null);
            if(null == propertyValue)
                continue;

            stringBuilder.Append(propertyValue.ToString());
        }

        return stringBuilder.ToString();
    }

    protected void RegisterProperty(
        Expression<Func<T, Object>> expression)
    {
        Check.Argument(expression, "expression").IsNotNull();

        MemberExpression memberExpression;
        if(ExpressionType.Convert == expression.Body.NodeType)
        {
            var body = (UnaryExpression)expression.Body;
            memberExpression = body.Operand as MemberExpression;
        }
        else
        {
            memberExpression = expression.Body as MemberExpression;
        }

        if(null == memberExpression)
        {
            var message = ResourceLoader<ValueObject<T>>
                              .GetString("InvalidMemberExpression");
            throw new InvalidOperationException(message);
        }

        Properties.Add(memberExpression.Member as PropertyInfo);
    }
}

This generic base class takes care of the equality by overriding Equals and GetHashCode from the Object class and implementing the IEquality interface. It also takes care of a default implementation of the ToString method.

Using this base class significantly reduces the amount of code for implementing  a value object in the domain.

public class Tag : ValueObject<Tag>
{
    public String Name { get; private set; }

    public Tag(String name)
    {
        Name = name;
        RegisterProperty(value => value.Name);
    }
}

And that is that. The only thing I’ve omitted in this example is the validation of the specified name.

I’ve been using this base class in a couple of projects now, and so far, I’ve been very pleased with the results although it can always be improved. I’d love to read your comments.

  • http://elegantcode.com Jan Van Ryswyck

    @Gustavo: I tried writing a failing test, but the method doesn’t fail. The line that follows the line of code you mentioned will deal with the otherValue being a null reference.

  • Gustavo Ringel

    Excuse me, i didn’t subscribe to the comments and didn’t pay attention you answered.
    This arrives to an exception copying your code and writing in the main of a console app:

    Tag cl1 = new Tag(null);
    Tag cl2 = new Tag(null);

    try
    {
    Console.WriteLine(cl1.Equals(cl2));
    Console.WriteLine(cl2.Equals(cl1));
    }
    catch (Exception ex)
    {
    Console.WriteLine(ex.ToString());
    }

    With my change that doesn’t fail

  • http://elegantcode.com/ Jan Van Ryswyck

    @Gustavo: You are right, there was a bug in there. I guess I already discovered that earlier on as I still can’t write a failing test. I made a diff between the code in the repository and the code from the blog and found that the post contained the following line:

    if(null == oneValue && null != otherValue) return false;

    while my repository contained:

    if(null == oneValue || null != otherValue) return false;

    The latter seems to fix this issue. Thx a lot for the feedback.

  • Christian Deger

    @Jan Van Ryswyck

    I still think Gustavos solution is the right one.

    Your updated repository version:

    if(null == oneValue || null != otherValue) return false;

    Does allways return false when otherValue is not null. And that does not seem right.

    Besides that, I do like your implementation. Thank You!

    -Christian

  • http://elegantcode.com Jan Van Ryswyck

    @Christian: You’re right. I don’t seem to be able to even copy a single line of code to WLW. I need a vacation.

  • sadfsdf

    What an incredible waste of time.