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 IEquatable<CourseCode>
interface.
As discussed in my recent code gardening post on helper methods,
implementing 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 courseCode
value
was null. If a null Code
was valid, we’d need to handle that explicitly within Equals()
by checking whether courseCode
was null.
We can now implement both Equals()
and the closely related GetHashCode()
.
Our implementations of Equals()
and 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.
A static Equals()
method is useful to have, and makes implementation of the ==
and !=
operators simple as well.
Why implement the operators? Without them, any code that tries to use the standard ==
and !=
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 CourseCode
instances.
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 - CompareTo()
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.
Comments
blog comments powered by Disqus