Creating custom types to represent specific values in your application is a proven way to prevent defects in your code. New features in C# 6 make it easier to write these types - and in this post, we’ll explore how to write a semantic type that plays well in the C# world.
To begin, let’s define a very simple immutable semantic type to capture a course code - the unique code that used to identify a course of study.
In this case, I’ve chosen to implement the type as a sealed class. This means that I can ensure that every instance is valid (something that can’t occur with a struct), but with the need to deal with null values. Declaring the type as a struct would exchange those concerns - we wouldn’t need to deal with null values, but it would be possible to create an invalid (or empty) instance.
To avoid creating an invalid instance, we need a method to test whether a string course code is valid. Once we have that, we need to use it to check the provided valid within the constructor.
The definition for
IsValidCode() shows the new expression bodied syntax, a convenience for
defining simple methods and properties. In simple cases like this, these can now be written in a
single statement - often a single line.
You can also see the new
nameof operator, used to provide the
ArgumentException with the name
of the argument.
We need to be able to compare different
CourseCode instances to see if they are equivalent, so we
need to override
Equals(), and that means we need to override
GetHashCode() as well.
To achieve this we’ll work towards it piecewise, beginning with a method to standardize our
comparisons and with an implementation of the
As discussed in my recent code gardening post on helper methods,
HasCode() helps to ensure consistency of comparison so that different parts of our
system use the same consistent approach for the test.
Our previously implemented
IsValidCode() ensures we never have a null value, so we can use
?. (known to some as the Elvis operator) to handle the case when the passed
was null. If a null
Code was valid, we’d need to handle that explicitly within
by checking whether
courseCode was null.
We can now implement both
Equals() and the closely related
Our implementations of
GetHashCode() need to be consistent with each other if our
semantic type is to behave properly as the key of a
Dictionary<CourseCode,T>. Critically, we need
not use the dictionary ourselves - some of the LINQ operators (and other parts of the .Net
framework) use dictionaries under the covers, so the correct implementation is vital.
Equals() method is useful to have, and makes implementation of the
operators simple as well.
Why implement the operators? Without them, any code that tries to use the standard
operators to compare values will be working from reference equality instead of value equality and
the code probably won’t do what’s wanted.
To round off our semantic class, an implementation of the
IComparable<CourseCode> interface to
ensure we can sort
Note that we need to use the same
StringComparison option here as we did in the original
HasCode() method so that our sorting semantics align with our equality semantics -
must return zero precisely when
Equals() returns true.
With all of these pieces together, we’ve created a full
CourseCode semantic type implementation,
a fully functional class that will behave well in the .NET ecosystem.