Creating an immutable value object in C# - Part V - Using a library - Luca Bolognese

Creating an immutable value object in C# - Part V - Using a library

Luca -

☕ 2 min. read

Other posts:

  • Part I - Using a class

    • Part II - Making the class bet­ter

      • Part III - Using a struct
      • Part IV - A class with a spe­cial value In the last post we pre­sented a vari­a­tion of im­ple­ment­ing a value ob­ject us­ing a class. Everything works (obviously), but the amount of code to write is un­pleas­ing. In this post we ex­am­ine a li­brary based so­lu­tion. I just de­scribe how to use the Record class, not how it is im­ple­mented. You can read the at­tached im­ple­men­ta­tion code (it is in func­tional.cs). There is much more in there than Record<>. I’ll talk about the rest in a (hopefully) up­com­ing se­ries.

      To use the record class you need to in­herit from it, as in:

      public class DateSpan: Record<DateTime, DateTime, bool> {...}

      The generic ar­gu­ment types rep­re­sent the types that com­prise the (immutable) state of the ob­ject. You then need a friendly way for folks to ac­cess this state:

      public DateTime Start { get { return state.Item1; } }
      

    pub­lic DateTime End { get { re­turn state.Item2; } } pub­lic bool HasValue { get { re­turn 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>&lt;<span style="color:rgb(0,0,255);">string</span>, <span style="color:rgb(0,0,255);">int</span>&gt; {
    <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; } }
    

    } pub­lic class Customer: Record<string, IEnumerable<Order>> { pub­lic Customer(string name, IEnumerable<Order> or­ders) : base(name, or­ders) { } pub­lic string Name { get { re­turn state.Item1; } } pub­lic IEnumerable<Order> Orders { get { re­turn 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 &#8216;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)
12 Comments

Comments

Community Blogs

2008-01-16T18:44:56Z

For 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:40Z

Welcome to the XXXIX issue of Community Convergence. The big news this week is that Microsoft has begun

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

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

Well, 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?

Yeah, 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.

Isn't code generation a much better option at this time, while 1st class readonly classes aren't available?

It 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).

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

All 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:25Z

Previous posts: Part I - Background Tuples are a way for you not to name things. In Object Oriented languages

0 Webmentions

These are webmentions via the IndieWeb and webmention.io.