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.

public sealed class CourseCode
{
    public string Code { get; }

    public CourseCode(string code)
    {
        Code = code;
    }
}

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.

public static bool IsValidCode(string code)
    => !string.IsNullOrEmpty(code);

public CourseCode(string code)
{
    if (!IsValidCode(code))
    {
        throw new ArgumentException("Invalid code", nameof(code));
    }

    Code = code;
}

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.

public bool HasCode(string code)
    => string.Equals(code, Code, 
        StringComparison.InvariantCultureIgnoreCase);

public bool Equals(CourseCode courseCode)
    => ReferenceEquals(this, courseCode) 
        || HasCode(courseCode?.Code);

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().

public override bool Equals(object instance) => Equals(instance as CourseCode);

public override int GetHashCode() => Code.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.

public static bool Equals(CourseCode left, CourseCode right)
    => left?.Equals(right) 
        ?? ReferenceEquals(left, right);

public static bool operator ==(CourseCode left, CourseCode right)
    => Equals(left, right);

public static bool operator !=(CourseCode left, CourseCode right)
    => !Equals(left, right);

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.

public int CompareTo(CourseCode other)
    => string.Compare(Code, other?.Code, 
        StringComparison.InvariantCultureIgnoreCase);

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
Next Post
Semantic Types Redux  05 Jun 2016
Prior Post
Multiple Boolean Parameters are Really Evil  22 May 2016
Related Posts
Browsers and WSL  31 Mar 2024
Factory methods and functions  05 Mar 2023
Using Constructors  27 Feb 2023
An Inconvenient API  18 Feb 2023
Method Archetypes  11 Sep 2022
A bash puzzle, solved  02 Jul 2022
A bash puzzle  25 Jun 2022
Improve your troubleshooting by aggregating errors  11 Jun 2022
Improve your troubleshooting by wrapping errors  28 May 2022
Keep your promises  14 May 2022
Archives
May 2016
2016