Creating Immutable Data Classes in C#… Quickly
Tuesday, May 26th, 2009Of late, I’ve been tempted by F#. In reading articles and familiarizing myself with the language, there seems to be something truly interesting there, but I just don’t grok functional programming yet.
One item in particular that I’ve seen is the Record data expression. This allows you to create an immutable data structure, like so:
type Data = { Count : int; Name : string } let data1 = { Count = 3; Name = "Hello"; }
Then, if at a later time you wish to change Count but keep Name the same, that’s absurdly simple.
let newData = data1 with Count = 4;
C#… does not have anything like this. To create a similar immutable data structure (including equality checking), you’d have to do something like this:
using System; using System.Collections.Generic; public class Data : IEquatable<Data> { private readonly int mCount; public int Count { get { return mCount; } } private readonly string mName; public string Name { get { return mName; } } public Data( int aCount, string aName ) { mCount = aCount; mName = aName; return; } public Data ChangeCount( int aCount ) { return new Data( aCount, mName ); } public Data ChangeName( string aName ) { return new Data( mCount, aName ); } public bool Equals( Data aOther ) { bool lResult = false; if ( aOther != null ) { lResult = ( this.Count == aOther.Count && this.Name == aOther.Name ); } return lResult; } public override bool Equals( object aOther ) { bool lResult = false; if ( aOther is Data ) { lResult = Equals( aOther as Data ); } return lResult; } public override int GetHashCode() { return Name.GetHashCode(); } public static bool operator ==( Data aLeft, Data aRight ) { return EqualityComparer<Data>.Default.Equals( aLeft, aRight ); } public static bool operator !=( Data aLeft, Data aRight ) { return !EqualityComparer<Data>.Default.Equals( aLeft, aRight ); } }
That’s a lot of work! In F#, what takes a single line takes 70 in C#. And a lot of that is nasty, crufty copy-paste code. And its easy to forget to change some things, like the Equals method.
Well, let me correct myself. That would be a lot of work… if I wrote that. But I didn’t. I just wrote this:
<# this.Namespace = "Example"; this.ClassName = "Data"; this.ClassScope = "public"; this.ImplementINotifyProperty = false; this.HashCodeVariable = "Name"; this.Variables = new List<Variable>() { new Variable( "Count", "int", Options.Immutable | Options.ChangeMethod ), new Variable( "Name", "string", Options.Immutable | Options.ChangeMethod ), }; #> <#@ include file="DataClass.tt" #>
What is this mysterious mumbo-jumbo? That, sir (or madam), is T4: Microsoft’s Text Template Transformation Toolkit (aka a code generator). It is included in Visual Studio 2008, but it isn’t widely advertised.
My DataClass.tt file is a horribly messy template that takes in the values I listed above, and translates them into the real source code example I pasted above. Besides immutable data structures, it also supports mutable members, including support for INotifyPropertyChanged and [Member]Changed events.
I’m rather liking this so far. If it turns out that this code generation is a horrible idea, I can simply delete my .tt files and use my auto-generated .cs files from then-on with little loss.
Note that the following source code is not well documented, and is not very error tolerant. If you do something silly, chances are you’ll get an obtuse compilation error. I recommend modifying it to suit your own needs.
Data Class Code Generator Source Code (11 KB, requires Visual Studio 2008)