Welcome to MSDN Blogs Sign in | Join | Help

Creating an immutable value object in C# - Part II - Making the class better

Other posts:

In the previous post I showed how to trivially implement a value object. The code works but it has several issues. Some are very simple, others are more interesting.

Let's take a look at them:

  • State not explicitly read-only
  • Asymmetry in the usage of Union and Intersection
  • Small perf issue in the Union method

The first problem is that my use of automatic properties doesn't assure that the status of the object is immutable; I can still modify it from inside the class. The simple solution is to make the fields readonly and write the getters as in:

    private readonly DateTime start;
    private readonly DateTime end;

    public DateTime Start { get { return start; } }
    public DateTime End { get { return end; } }

The second issue is more subtle. Consider this code

        DateSpan d1 = new DateSpan(new DateTime(1, 1, 2000), new DateTime(1, 1, 2002));
        DateSpan d2 = null;

        DateSpan r1 = d1.Intersect(d2);
        DateSpan r2 = d2.Intersect(d1);

I would like things in my little 'algebra' to be symmetric. In this case I'd like: r1 == r2 == null. But this code throws in the last line as I'm trying to invoke a method on a null object.

The traditional solution to this problem is to make Union and Intersect to be static methods, but then you loose the magic of calling them as instance methods (i.e. it becomes hard to chain them together as in d1.Intersect(d2).Union(d3).Intersect(d4)).

Extension methods come to the rescue here as they allow you to create static methods, but to call them as if the were instance methods. The new code for Intersect looks like the following:

    public static DateSpan Intersect(this DateSpan one, DateSpan other) {

        if (one == null)
            return null;

        if (other == null)
            return null;

        if (one.IsOutside(other))
            return null;

        DateTime start = other.Start > one.Start ? other.Start : one.Start;
        DateTime end = other.End < one.End ? other.End : one.End;

        return new DateSpan(start, end);
    }

This workaround would not work if the extension method needs to access private state of the class. In that case you would need to create a static method on the DataSpan class and invoke it from the extension method. Slightly more convoluted, but still doable.

At a more philosophical level, the asymmetry issue happens here because I'm using something outside my domain of interest (the null value) to represent a special value inside my domain of interest. More on this as we talk about structs in upcoming posts.

The last point is a very small one. In the Union method I am creating a new object unnecessarily in the following line:

        if (other == null)
            return new DateSpan(Start, End);

I can obviously avoid it by just returning "this".

This post hints to several possible issues. Is it a good idea to use null to represent special values in my domain? What if I have more than one of them (i.e. positive/negative infinite)? Would using structs solve these problems?

We'll take a look at these options in upcoming posts. Attached is the modified code.

Published Thursday, December 06, 2007 9:34 AM by lucabol
Filed under:

Comments

# Luca Bolognese's WebLog : Creating an immutable value object in C# - Part I - Using a class

# re: Creating an immutable value object in C# - Part II - Making the class better

This is shaping up to be a very timely and useful mini series Luca :-) Please don't keep us waiting too long for the next part!

Friday, December 07, 2007 7:30 AM by Tom Kirby-Green

# re: Creating an immutable value object in C# - Part II - Making the class better

First off, the most practical representation of date spans is an inclusive lower bound and exclusive upper bound, i.e., [start, end). Equally important, they should be treated as points in time (which is what DateTime represents), not complete days. Thus, new DateTime(d, d) is empty for any d (solving empty ranges) and new DateTime(d, d.AddDays(1)) is exactly one day.

Also, the type should really be DateTimeSpan.

Convenience properties such as a static DateSpan.Empty would come in handy.

Finally, DateTime has MaxValue and MinValue, which serve as fairly natural surrogates for +/- infinity, and also eliminate edge-cases from set operations.

Friday, December 21, 2007 2:46 AM by Marcelo Cantos

# re: Creating an immutable value object in C# - Part II - Making the class better

Oops! Wherever I said 'new DateTime', I meant 'new DateSpan'.

Friday, December 21, 2007 2:48 AM by Marcelo Cantos

# re: Creating an immutable value object in C# - Part II - Making the class better

Thanks for the comment. It makes me think of something an old functional guy said once: "The idea of reusing objects across domain boundaries is absurd, not even something as simple as Person can be defined the same way in different domains".

In my domain (a stock backtesting app) a DateSpan needs to have a day boundary, not a point in time boundary. Also, inclusive lower and upper bounds have been working pretty well for my app so far (even if I can see that your definition has conceptual appeal).

And anyhow, I'm just trying to show how to use some language features. I don't care much about the particular sample. I could have chosen Complex, but I thought it was too boring ...

Friday, December 21, 2007 12:36 PM by lucabol

# Creating an immutable value object in C# - Part III - Using a struct

Other posts: Part I - Using a class Part II - Making the class better In Part II I talked about the asymmetry

Monday, December 24, 2007 5:39 PM by Luca Bolognese's WebLog

# Creating an immutable value object in C# - Part III - Using a struct

Other posts: Part I - Using a class Part II - Making the class better In Part II I talked about the asymmetry

Monday, December 24, 2007 6:01 PM by Noticias externas

# Creating an immutable value object in C# - Part IV - A class with a special value

Other posts: Part I - Using a class Part II - Making the class better Part III - Using a struct In the

Friday, December 28, 2007 6:45 PM by Luca Bolognese's WebLog

# Creating an immutable value object in C# - Part IV - A class with a special value

Other posts: Part I - Using a class Part II - Making the class better Part III - Using a struct In the

Friday, December 28, 2007 7:06 PM by Noticias externas

# re: Creating an immutable value object in C# - Part II - Making the class better

Luca: Consider...

public DateTime Start { get; private set; }

Saturday, December 29, 2007 12:54 AM by kfarmer@microsoft.com

# re: Creating an immutable value object in C# - Part II - Making the class better

Hey Kit,

Automatic properties don't prevent setting the property from inside the class. The readonly keyword does.

Saturday, December 29, 2007 6:15 PM by lucabol

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

Other posts: Part I - Using a class Part II - Making the class better Part III - Using a struct Part

Friday, January 11, 2008 1:36 PM by Luca Bolognese's WebLog

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

Other posts: Part I - Using a class Part II - Making the class better Part III - Using a struct Part

Friday, January 11, 2008 1:52 PM by Noticias externas

# Immutability in C#

For some reason, there's been a lot of buzz lately around immutability in C#. If you're interested in

Wednesday, January 16, 2008 6:36 PM by Tales from the Evil Empire
New Comments to this post are disabled
 
Page view tracker