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.

Jan Van Ryswyck C#, DDD

  1. July 15th, 2009 at 12:03 | #1

    @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.

  2. sadfsdf
    July 15th, 2009 at 16:25 | #2

    What an incredible waste of time.

Comment pages
Comments are closed.