Immutability is one of those programming subjects we hear about fairly regularly at the moment. It’s supposed to be one of those principles which make parallel programming easier, and since we’re currently in the world of 4-core machines, parallel is big right now.
Unfortunately it’s really hard in C# to create immutable objects. Or at least, C# isn’t really designed for it, so if you try to write an app using immutable data structures, you may find yourself writing a lot of boilerplate just to adhere to this particular paradigm. Which means at some point you’ll stop bothering, because it’s just too much work.
So, in the interests of reinventing the wheel, I’ve written a small code generator which creates immutable structs, which have ‘mutator’ functions. Mutators are methods which return whole new objects with just one property changed. For instance, if you have an immutable rectangle defined like this;
var firstRect = new Salamander.Models.Rectangle(100, 100, 100, 100);
then you can create a new object will all the same properties except one like this;
var secondRect = firstRect.WithTop(50);
Anyway, the system consists of two files; the generator itself [(Immutable.ttinc)](https://stevecooper.org/documents/Immutable.ttinc) and a file which defines the immutable types to create [(myImmutableTypes.tt)](https://stevecooper.org/documents/myImmutableTypes.tt). You’ll need to include them both in your c# project, then right-click the `.tt` file and choose ‘Run Custom Tool’
## To define your own custom types
You’ll need to look inside the `.tt` file and write a line which describes the type you’d like to create — basically just a namespace-qualified type name, and a list of fields;
GenerateImmutable(“Db.Person: string FirstName; string SecondName”, “A Person”);
The second parameter is a class-level xml comment
## Immutable.ttinc
<#+
private class Member
{
public string Type;
public string Name;
}
void GenerateImmutable(string definition)
{
GenerateImmutable(definition, string.Empty);
}
void GenerateImmutable(string definition, string comment)
{
var typeBuilder = new System.Text.StringBuilder();
int indexOfColon = definition.IndexOf(':');
if (indexOfColon == -1)
{
Error("No colon; expected, eg 'Rectangle: ‘”);
}
string ns = “”;
string typeName = definition.Split(‘:’)[0];
string members = definition.Split(‘:’)[1];
//
// NAMESPACE
//
int lastNsDot = typeName.LastIndexOf(“.”);
if (lastNsDot != -1)
{
ns = typeName.Substring(0, lastNsDot);
typeName = typeName.Substring(lastNsDot+1);
}
if (String.IsNullOrEmpty(ns) == false)
{
typeBuilder.Append(“namespace “).AppendLine(ns).AppendLine(“{“);
}
//
// TYPE DEFINITION
//
typeBuilder.Append(” ///
“).Append(comment).AppendLine(“
“);
typeBuilder.Append(” public partial struct “).AppendLine(typeName).AppendLine(” {“);
var memberList = members
.Split(‘;’)
.Select(x => x.Trim())
.Where(x => x.Length > 0)
.Select(x => x.Split(‘ ‘))
.Select(arr => new Member { Type = arr[0], Name=arr[1] })
;
//
// CONSTRUCTOR
//
typeBuilder.Append(” ///
Create a new “).Append(typeName).Append(“
“).AppendLine();
typeBuilder.Append(” public “).Append(typeName).Append(“(“);
foreach(var memberItem in memberList)
{
typeBuilder.Append(memberItem.Type).Append(” “).Append(memberItem.Name).Append(“,”);
}
typeBuilder.Length = typeBuilder.Length-1; // remove trailing comma
typeBuilder.AppendLine(“)”);
typeBuilder.AppendLine(” {“);
foreach(var memberItem in memberList)
{
typeBuilder
.Append(” this.”)
.Append(memberItem.Name)
.Append(” = “)
.Append(memberItem.Name)
.Append(“;”)
.AppendLine();
}
typeBuilder.AppendLine(” }”);
//
// fields
//
typeBuilder.AppendLine(” // fields”);
foreach(var memberItem in memberList)
{
typeBuilder
.AppendFormat(” public readonly {0} {1};”, memberItem.Type, memberItem.Name)
.AppendLine();
}
typeBuilder.AppendLine();
foreach(var memberItem in memberList)
{
typeBuilder
.AppendLine(” ///
mutator for ” + memberItem.Name + ” property
“)
.AppendFormat(” public {0} With{1}({2} new{1})”, typeName, memberItem.Name, memberItem.Type)
.AppendLine(” {“)
.AppendFormat(” var newItem = new {0}(“, typeName)
;
foreach(var memberItemParam in memberList)
{
if (memberItem.Name == memberItemParam.Name)
{
typeBuilder.Append(“new”).Append(memberItem.Name).Append(“,”);
}
else
{
typeBuilder.Append(“this.”).Append(memberItemParam.Name).Append(“,”);
}
}
typeBuilder.Length = typeBuilder.Length-1; // remove trailing comma
typeBuilder.AppendLine(“);”);
typeBuilder.AppendLine(” return newItem;”);
typeBuilder.AppendLine(” }”);
typeBuilder.AppendLine();
}
typeBuilder.AppendLine(” }”);
if (String.IsNullOrEmpty(ns) == false)
{
typeBuilder.AppendLine(“}”);
}
Write(typeBuilder.ToString());
}
#>
## myImmutableTypes.tt