Creating an immutable value object in C# - Part V - Using a library
Luca -
☕ 2 min. read
Other posts:
-
-
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: <pre class="code"><span style="color:rgb(0,0,255);">public</span> <span style="color:rgb(0,0,255);">class</span> <span style="color:rgb(43,145,175);">Order</span>: <span style="color:rgb(43,145,175);">Record</span><<span style="color:rgb(0,0,255);">string</span>, <span style="color:rgb(0,0,255);">int</span>> { <span style="color:rgb(0,0,255);">public</span> Order(<span style="color:rgb(0,0,255);">string</span> item, <span style="color:rgb(0,0,255);">int</span> qty): <span style="color:rgb(0,0,255);">base</span>(item,qty) {} <span style="color:rgb(0,0,255);">public</span> <span style="color:rgb(0,0,255);">string</span> Item { <span style="color:rgb(0,0,255);">get</span> { <span style="color:rgb(0,0,255);">return</span> state.Item1;}} <span style="color:rgb(0,0,255);">public</span> <span style="color:rgb(0,0,255);">int</span> Quantity { <span style="color:rgb(0,0,255);">get</span> { <span style="color:rgb(0,0,255);">return</span> 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: <pre class="code">[<span style="color:rgb(43,145,175);">TestMethod</span>] <span style="color:rgb(0,0,255);">public</span> <span style="color:rgb(0,0,255);">void</span> Record2Test() { <span style="color:rgb(0,0,255);">var</span> c1 = <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Customer</span>(<span style="color:rgb(163,21,21);">"Luca"</span>, <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>[] { <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"car"</span>,1), <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"stereo"</span>, 3)}); <span style="color:rgb(0,0,255);">var</span> c11 = <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Customer</span>(<span style="color:rgb(163,21,21);">"Luca"</span>, <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>[] { <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"car"</span>, 1), <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"stereo"</span>, 3) }); <span style="color:rgb(0,0,255);">var</span> c2 = <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Customer</span>(<span style="color:rgb(163,21,21);">"Bob"</span>, <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>[] { <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"car"</span>, 1), <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"stereo"</span>, 3) }); <span style="color:rgb(0,0,255);">var</span> c3 = <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Customer</span>(<span style="color:rgb(163,21,21);">"Bob"</span>, <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>[] { <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"car"</span>, 1), <span style="color:rgb(0,0,255);">new</span> <span style="color:rgb(43,145,175);">Order</span>(<span style="color:rgb(163,21,21);">"stereo"</span>, 2) }); <span style="color:rgb(43,145,175);">Assert</span>.AreEqual(c1, c11); <span style="color:rgb(43,145,175);">Assert</span>.AreNotEqual(c1, c2); <span style="color:rgb(43,145,175);">Assert</span>.AreNotEqual(c1, c3); <span style="color:rgb(43,145,175);">Assert</span>.AreNotEqual(c2, c3); }</pre> 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. [TimeLineUsingRecord.zip](https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/07/07/77/13/TimeLineUsingRecord.zip)
-
0 Webmentions
These are webmentions via the IndieWeb and webmention.io.
12 Comments
Comments
Geek Lectures - Things geeks s
2008-01-11T14:25:38ZPingBack from http://geeklectures.info/2008/01/11/creating-an-immutable-value-object-in-c-part-v-using-a-library/
Community Blogs
2008-01-16T18:44:56ZFor some reason, there's been a lot of buzz lately around immutability in C#. If you're interested in
Charlie Calvert's Community Bl
2008-01-21T01:45:40ZWelcome to the XXXIX issue of Community Convergence. The big news this week is that Microsoft has begun
mg
2008-02-01T20:06:12ZNice work!
One question about "stealing" the base class - what about introducing another layer of abstraction and instead of declaring your type like this:
public class DateSpan: Record<DateTime, DateTime, bool> {...}
do something like this:
public class DateSpan: ValueObject<Record<DateTime, DateTime, bool>> {...}
The ValueObject class would then expose the record to as a protected property and pass Equals, ToString... to the Record object. It would make the declarations a little bit more complicated, but your biggest drawback is not a problem anymore.
lucabol
2008-02-04T12:07:41ZI think I'm missing something. Aren't you still stealing the base class with the ValueObject class?
I.E. you cannot declare DateSpan to inherit from a MySpan class.
mg
2008-02-04T14:25:00ZWell, I was thinking about ValueObject more in terms of a type system base class, where you can put your additional behaviour and not spoil the Record class. You are right that still you cannot have a deeper hierarchy of types with this solution.
BTW: don't you think it would be nice to have a language support for immutable types? Something like:
public readonly class MyClass...
where all fields are readonly fields of readonly types?
lucabol
2008-02-04T14:44:49ZYeah, you are right.
We talked about having first class readonly classes a bunch of times, but we never came up with a proposal we are happy with. We are still discussing the topic.
Doekman
2008-02-06T05:25:49ZIsn't code generation a much better option at this time, while 1st class readonly classes aren't available?
lucabol
2008-02-06T11:02:03ZIt is an option. I wouldn't say it is better. It has pros and cons (i.e. readibility, amount of code, mantainability).
As for me, I prefer library solutions to codegen whenever available (and roughly usable).
qq
2008-02-08T16:53:24ZI think you guys in immutable space have frankly lost it. It is not new, it is common principles that do not apply in all fields, and just because functional attempts are again popular you are hitting on attempting to solve a problem with a wrong tool: .net type system.
If you think magically you will somehow parallelise code and algorithms, you are in for a big 'immutable surprise'.
No, silver, bullet..
lucabol
2008-02-08T17:32:41ZAll these posts say is: iff you need an immutable class, here are a bunch of options of how to do it in C#.
I don't think I'm claiming anything more than that. Am I?
Luca Bolognese's WebLog
2008-04-08T17:06:25ZPrevious posts: Part I - Background Tuples are a way for you not to name things. In Object Oriented languages