Other posts:
- Part I - Using a class
- Part II - Making the class better
- Part III - Using a struct
- Part IV - A class with a special value
In the last post we presented a variation of implementing a value object using a class. Everything works (obviously), but the amount of code to write is unpleasing. In this post we examine a library based solution. I just describe how to use the Record class, not how it is implemented. You can read the attached implementation code (it is in functional.cs). There is much more in there than Record<>. I’ll talk about the rest in a (hopefully) upcoming series.
To use the record class you need to inherit from it, as in:
public class DateSpan: Record<DateTime, DateTime, bool> {...}
The generic argument types represent the types that comprise the (immutable) state of the object. You then need a friendly way for folks to access this state:
public DateTime Start { get { return state.Item1; } }
public DateTime End { get { return state.Item2; } }
public bool HasValue { get { return state.Item3; } }
This is all you have to do. You don’t need to implement Equals, ==, != and GetHashCode. Structural equivalence is given to you by the Record class. Such a property is recursive, in the sense that you can embed value objects inside other value objects and the implementation would walk your object graph as necessary.
For example, given the following class hierarchy:
public class Order: Record<string, int> {
public Order(string item, int qty): base(item,qty) {}
public string Item { get { return state.Item1;}}
public int Quantity { get { return state.Item2; } }
}
public class Customer: Record<string, IEnumerable<Order>> {
public Customer(string name, IEnumerable<Order> orders) : base(name, orders) { }
public string Name { get { return state.Item1; } }
public IEnumerable<Order> Orders { get { return state.Item2; } }
}
The following test case succeed:
[TestMethod]
public void Record2Test() {
var c1 = new Customer("Luca", new Order[] { new Order("car",1), new Order("stereo", 3)});
var c11 = new Customer("Luca", new Order[] { new Order("car", 1), new Order("stereo", 3) });
var c2 = new Customer("Bob", new Order[] { new Order("car", 1), new Order("stereo", 3) });
var c3 = new Customer("Bob", new Order[] { new Order("car", 1), new Order("stereo", 2) });
Assert.AreEqual(c1, c11);
Assert.AreNotEqual(c1, c2);
Assert.AreNotEqual(c1, c3);
Assert.AreNotEqual(c2, c3);
}
Please don’t take my library as production ready code. The amount of test I put into it is limited. You can probably find obvious bugs with it.
Let’s look at other drawbacks. The biggest one is that I’m stealing your base class. If you want your value object to inherit from something else, you cannot. You cannot even have value objects inherit from each other. In that case you are back to implementing your own Equals, == and so on. The only tools at your disposal are interfaces and composition.
Another drawback is that writing classes in this way is slightly unnatural. You have to think about the ‘type’ of your state in the declaration of the class itself instead of more naturally writing it closer to where you assign names to it (property/field declaration).
Having considered these drawbacks, I’m using this library in all my code wherever I need value objects (which is almost everywhere these days). Writing all the Equals explicitly is too error prone for my taste. I will also be creating IDE snippets for myself that make writing these classes easier.
I don’t think I have anything else to say on this topic, so this will be my last post on it. If something else comes up, I’ll let you know.
View comments on GitHub or email me