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

-

Other posts:

In the pre­vi­ous post I showed how to triv­ially im­ple­ment a value ob­ject. The code works but it has sev­eral is­sues. Some are very sim­ple, oth­ers are more in­ter­est­ing.

Let’s take a look at them:

The first prob­lem is that my use of au­to­matic prop­er­ties does­n’t as­sure that the sta­tus of the ob­ject is im­mutable; I can still mod­ify it from in­side the class. The sim­ple so­lu­tion is to make the fields read­only and write the get­ters as in:

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

The sec­ond is­sue is more sub­tle. 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 lit­tle algebra’ to be sym­met­ric. In this case I’d like: r1 == r2 == null. But this code throws in the last line as I’m try­ing to in­voke a method on a null ob­ject.

The tra­di­tional so­lu­tion to this prob­lem is to make Union and Intersect to be sta­tic meth­ods, but then you loose the magic of call­ing them as in­stance meth­ods (i.e. it be­comes hard to chain them to­gether as in d1.In­ter­sect(d2).Union(d3).In­ter­sect(d4)).

Extension meth­ods come to the res­cue here as they al­low you to cre­ate sta­tic meth­ods, but to call them as if the were in­stance meth­ods. The new code for Intersect looks like the fol­low­ing:

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 ex­ten­sion method needs to ac­cess pri­vate state of the class. In that case you would need to cre­ate a sta­tic method on the DataSpan class and in­voke it from the ex­ten­sion method. Slightly more con­vo­luted, but still doable.

At a more philo­soph­i­cal level, the asym­me­try is­sue hap­pens here be­cause I’m us­ing some­thing out­side my do­main of in­ter­est (the null value) to rep­re­sent a spe­cial value in­side my do­main of in­ter­est. More on this as we talk about structs in up­com­ing posts.

The last point is a very small one. In the Union method I am cre­at­ing a new ob­ject un­nec­es­sar­ily in the fol­low­ing line:

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

I can ob­vi­ously avoid it by just re­turn­ing this.

This post hints to sev­eral pos­si­ble is­sues. Is it a good idea to use null to rep­re­sent spe­cial val­ues in my do­main? What if I have more than one of them (i.e. pos­i­tive/​neg­a­tive in­fi­nite)? Would us­ing structs solve these prob­lems?

We’ll take a look at these op­tions in up­com­ing posts. Attached is the mod­i­fied code.

Tags

16 Comments

Comments

Tom Kirby-Green

2007-12-07T07:30:56Z

This is shap­ing up to be a very timely and use­ful mini se­ries Luca :-) Please don’t keep us wait­ing too long for the next part!

Marcelo Cantos

2007-12-21T02:46:29Z

First off, the most prac­ti­cal rep­re­sen­ta­tion of date spans is an in­clu­sive lower bound and ex­clu­sive up­per bound, i.e., [start, end). Equally im­por­tant, they should be treated as points in time (which is what DateTime rep­re­sents), not com­plete days. Thus, new DateTime(d, d) is empty for any d (solving empty ranges) and new DateTime(d, d.Ad­d­Days(1)) is ex­actly one day.
Also, the type should re­ally be DateTimeSpan.
Convenience prop­er­ties such as a sta­tic DateSpan.Empty would come in handy.
Finally, DateTime has MaxValue and MinValue, which serve as fairly nat­ural sur­ro­gates for +/- in­fin­ity, and also elim­i­nate edge-cases from set op­er­a­tions.

Marcelo Cantos

2007-12-21T02:48:05Z

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

Thanks for the com­ment. It makes me think of some­thing an old func­tional guy said once: The idea of reusing ob­jects across do­main bound­aries is ab­surd, not even some­thing as sim­ple as Person can be de­fined the same way in dif­fer­ent do­mains”.
In my do­main (a stock back­test­ing app) a DateSpan needs to have a day bound­ary, not a point in time bound­ary. Also, in­clu­sive lower and up­per bounds have been work­ing pretty well for my app so far (even if I can see that your de­f­i­n­i­tion has con­cep­tual ap­peal).
And any­how, I’m just try­ing to show how to use some lan­guage fea­tures. I don’t care much about the par­tic­u­lar sam­ple. I could have cho­sen Complex, but I thought it was too bor­ing …

Luca Bolognese's WebLog

2007-12-24T17:39:25Z

Other posts: Part I - Using a class Part II - Making the class bet­ter In Part II I talked about the asym­me­try

Noticias externas

2007-12-24T18:01:25Z

Other posts: Part I - Using a class Part II - Making the class bet­ter In Part II I talked about the asym­me­try

Luca Bolognese's WebLog

2007-12-28T18:45:33Z

Other posts: Part I - Using a class Part II - Making the class bet­ter Part III - Using a struct In the

Noticias externas

2007-12-28T19:06:20Z

Other posts: Part I - Using a class Part II - Making the class bet­ter Part III - Using a struct In the

MSDNArchive

2007-12-29T00:54:21Z

Luca: Consider…
pub­lic DateTime Start { get; pri­vate set; }

Hey Kit,
Automatic prop­er­ties don’t pre­vent set­ting the prop­erty from in­side the class. The read­only key­word does.

Luca Bolognese's WebLog

2008-01-11T13:36:12Z

Other posts: Part I - Using a class Part II - Making the class bet­ter Part III - Using a struct Part

Noticias externas

2008-01-11T13:52:05Z

Other posts: Part I - Using a class Part II - Making the class bet­ter Part III - Using a struct Part

Tales from the Evil Empire

2008-01-16T18:36:53Z

For some rea­son, there’s been a lot of buzz lately around im­mutabil­ity in C#. If you’re in­ter­ested in

adamjcooper.com/blog

2008-06-03T15:37:59Z

The Quest for Quick-and-Easy Class-Based Immutable Value Objects in C# - Part 1: Introduction

adamjcooper.com/blog

2008-06-03T16:57:34Z

The Quest for Quick-and-Easy Immutable Value Objects in C#