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.
Stirling job. Nice Jan, I’ve been using Jimmy’s implementation for a while now too. A generic base VO is certainly a real time saver!
Perhaps an ‘auto’ registration (all public properties) by default, would be useful? Or a void RegisterAllPuplicProperties(this) member?
I’m not going to change VO impl part way through a project, but I’m tagging in delicious and will return back, post-current-project.
If one value is null and other values is not your function will crash.
It should had been something like:
if (oneValue == null) return otherValue == null;
Well, the previous comment should have started by saying i liked your work. I liked the static reflection here, and i took some parts of it. I just put attention on what a i previously commented.
Gustavo.
@Gustavo: Many thanks for your feedback. I’ll be writing a failing test soon. 🙂
Quality of the code aside, I don’t understand why others have stamped the idea of reflecting an object as belonging to this guy or that guy. Soon enough we’re gonna see software patents on these things. I’ve been using a similar pattern independently; heck, I actually reflect on the type upon type initialization, to cut some of the cost; Java has used dynabeans for a long time, but neither me nor the guys at Apache will claim co-authorship.
I guess this will slowly launch us in a frenzy of patenting our simplest bits of code on our blogs. It’s a bit like the frenzy with Joel Spolsky’s “Law of Leaky Abstractions” which is a piece of crap but I’ve seen it quoted quite a few times. Suddenly, the stupidest of bloggers could claim they’ve recognized a subtle law in some aspect of their work.
Look. The GoF didn’t claim they invented the patterns. They but cataloged them. The DDD community shouldn’t claim to have popularized the Value Object, except maybe to the .NET programmers. And this or that way of doing generic value objects aren’t this or that guy’s idea; they’re age old by now, really.
Thanks for posting per my right to dissent and to keep our put into our less wary readers some sense of personality.
@Mihai: I thought I made myself clear that I didn’t invent anything and that there are other people (Jimmy Bogard amongst others) who are doing this and have similar code. I’m also not claiming any ownership of the pattern. I just wanted to share some code that might be helpful to others and maybe get some feedback in the process. Lighten up.
@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.
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
@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.
@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
@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.
What an incredible waste of time.