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

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

Luca -

☕ 3 min. read

Other posts:

Well, to the un­trained eye they do. Structs can­not be null and they im­ple­ment Equals and GetHashCode by check­ing the state of the ob­ject, not its pointer in mem­ory.

So, have we found the per­fect tool to im­ple­ment our value ob­ject?

Unfortunately, no. Here is why a struct is a less than op­ti­mal way to im­ple­ment a value ob­ject:

  1. Convenience is­sues - it is not as con­ve­nient as it looks
    1. You still have to im­ple­ment ==’ and !=’.
      • You still want to im­ple­ment Equals() and GetHashCode(), if you want to avoid box­ing/​un­box­ing.
  2. Performance is­sues - it is not as fast as it looks
    1. Structs are al­lo­cated on the stack. Every time you pass them as ar­gu­ments, the state is copied. If your struct has more than a few fields, per­for­mance might suf­fer
    • Usability is­sues - it is not as use­ful as it looks.

      1. Structs al­ways have a pub­lic de­fault con­struc­tor that zeros’ all the fields
        • Structs can­not be ab­stract
          • Structs can­not ex­tend an­other structs
Don’t get me wrong, structs are ex­tremely use­ful as a way to rep­re­sent small bun­dles of data. But if you use value ob­jects ex­ten­sively, their lim­i­ta­tions start to show.
A case could be made that you should use struct to implement value objects if the issues exposed above don't apply to your case. When they do apply, you should use classes. I'm a forgetful and lazy programmer, I don't want to remember all these cases. I just want a pattern that I can use whenever I need a value object. It seems to me that structs don't fit the bill.

For the sake of completeness, here is the code for DateSpan using a struct. Note that I explicitly introduced a ‘special value' instead of using null (which is not available for structs).

<pre class="code"><span style="color:rgb(0,0,255);">using</span> System;

us­ing System.Collections.Generic; us­ing System.Linq; us­ing System.Text; pub­lic struct DateSpan { pub­lic sta­tic DateSpan NoValueDateSpan { get { re­turn no­Val­ue­DateS­pan; } } pub­lic DateSpan(DateTime pstart, DateTime pend) { if (pend < pstart) throw new ArgumentException(pstart.ToString() + ″ does­n’t come be­fore + pend.ToString()); start = pstart; end = pend; has­Value = true; } pub­lic DateSpan Union(DateSpan other) { if (!HasValue) re­turn other; if (!other.HasValue) re­turn this; if (IsOutside(other)) re­turn DateSpan.NoValueDateSpan; DateTime new­Start = other.Start < Start ? other.Start : Start; DateTime newEnd = other.End > End ? other.End : End; re­turn new DateSpan(newStart, newEnd); } pub­lic DateSpan Intersect(DateSpan other) { if (!HasValue) re­turn DateSpan.NoValueDateSpan; if (!other.HasValue) re­turn DateSpan.NoValueDateSpan; if (IsOutside(other)) re­turn DateSpan.NoValueDateSpan; DateTime new­Start = other.Start > Start ? other.Start : Start; DateTime newEnd = other.End < End ? other.End : End; re­turn new DateSpan(newStart, newEnd); } pub­lic DateTime Start { get { re­turn start; } } pub­lic DateTime End { get { re­turn end; } } pub­lic bool HasValue { get { re­turn has­Value; } } // Making field ex­plicitely read­only (but can­not use au­to­prop­er­ties) // BTW: If you want to use au­to­prop­er­ties, given that it is a struct, // you need to add :this() to the con­struc­tor pri­vate read­only DateTime start; pri­vate read­only DateTime end; pri­vate read­only bool has­Value; pri­vate bool IsOutside(DateSpan other) { re­turn other.start > end || other.end < start; } // Changing the in­ter­nal ma­chin­ery so that has­Value de­fault is false // This way the au­to­mat­i­cally gen­er­ated empty con­struc­tor re­turns the right thing pri­vate sta­tic DateSpan no­Val­ue­DateS­pan = new DateSpan(); #region Boilerplate Equals, ToString Implementation pub­lic over­ride string ToString() { re­turn string.Format(Start:{0} End:{1}”, start, end); } pub­lic sta­tic Boolean op­er­a­tor ==(DateSpan v1, DateSpan v2) { re­turn (v1.Equals(v2)); } pub­lic sta­tic Boolean op­er­a­tor !=(DateSpan v1, DateSpan v2) { re­turn !(v1 == v2); } //public over­ride bool Equals(object obj) { // if (this.GetType() != obj.Get­Type()) re­turn false; // DateSpan other = (DateSpan) obj; // re­turn other.end == end && other.start == start; //} //public over­ride int GetHashCode() { // re­turn start.GetH­ash­Code() | end.GetH­ash­Code(); //} #endregion }

                    [TimeLineAsStruct.zip](https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/06/85/58/26/TimeLineAsStruct.zip)
11 Comments

Comments

Luca Bolognese's WebLog

2007-12-28T18:45:34Z

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

Noticias externas

2007-12-28T19:06:21Z

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

Charlie Calvert's Community Bl

2008-01-02T16:13:59Z

Welcome to the thirty-eighth Community Convergence. These posts are designed to keep you in touch with

Combine the union and intersect into a single private function.  Add a 1 line wrapper function for the existing union and intersect functions which calls the combined function.
This is to ensure that the checking of parameter arguments and HasValue are done the same for both union and intersect (i..e, only one set of code to maintain.)
Change exception message so that the message identifies the object datatype (DateSpan) that is invalid (simplifies support calls and enhances maintainability)
Change
pstart.ToString() + " doesn't come before " + pend.ToString());
to
"DateSpan invalid: " + pstart.ToString() + " doesn't come before " + pend.ToString());

Luca Bolognese's WebLog

2008-01-11T13:36:13Z

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

Noticias externas

2008-01-11T13:52:09Z

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

Tales from the Evil Empire

2008-01-16T18:36:54Z

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

akhayre2000@yahoo.co.ukl

2008-01-18T08:01:36Z

i have get thart to t you that
{
}
{
would you thjat

adamjcooper.com/blog

2008-06-03T15:38:04Z

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

adamjcooper.com/blog

2008-06-03T16:57:49Z

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

0 Webmentions

These are webmentions via the IndieWeb and webmention.io.