~~ Offline ~~ theme Menu

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:

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.