The Guidelines Support Library is a small collection of utilities for C++. Today we’ll look at how two of them can make our C# code safer and cleaner.

NotNull

The C++ GSL has a not_null<T> type. Today’s C# version will be named in the standard C# way: NotNull<T>. The idea behind it is to wrap a value that is not a null reference. This allows code using a NotNull<T> variable to be sure it’s not dealing with null.

So how do we construct such a wrapper? It can be instructive to look at the design of the opposite type: Nullable<T>. That is a struct that contains a private T field accessed with the Value property. Let’s start out with that:

public struct NotNull<T>
{
    private readonly T value;
 
    public NotNull(T value)
    {
        this.value = value;
    }
 
    public T Value
    {
        get
        {
            return value;
        }
    }
}

This wrapper is very straightforward and applies no overhead to the wrapped value. However, it isn’t enforcing that the value is not null. To do that, let’s add a where constraint so that T is a class type that could be null. Then we can add an assertion to the constructor.

public struct NotNull<T>
    where T : class
{
    private readonly T value;
 
    public NotNull(T value)
    {
        UnityEngine.Assertions.Assert.IsFalse(
            object.ReferenceEquals(value, null),
            "Can't create a NotNull from a null value");
        this.value = value;
    }
 
    public T Value
    {
        get
        {
            return value;
        }
    }
}

If a user of NotNull<T> tries to pass null to the constructor then the assertion will fail in debug builds. The call to Assert.IsFalse and the string literal will be removed in release builds because Assert.IsFalse has a [Conditional("UNITY_ASSERTIONS")] attribute on it.

There’s still one issue remaining to address. Every struct has an implicit default constructor. This can be accessed with either new NotNull<T>() or default(T) to bypass our assertion. Since we can’t override this implicit default constructor, we can’t enforce a non-null value field. Instead, we’ll have to enforce that the Value property will never return null by adding an assertion there. This brings us to the final version of the type:

/// <summary>
/// Wrapper for a class object that is not a null reference. Conversions from
/// and to T are null-checked with Unity assertions.
/// </summary>
/// 
/// <author>
/// Jackson Dunstan, https://jacksondunstan.com/articles/4953
/// </author>
/// 
/// <license>
/// MIT
/// </license>
public struct NotNull<T>
    where T : class
{
    /// <summary>
    /// The held value. Can be null if this is default-constructed. Otherwise
    /// this is not null.
    /// </summary>
    private readonly T value;
 
    /// <summary>
    /// Wrap a non-null value.
    /// </summary>
    /// 
    /// <param name="value">
    /// Value to wrap. Must be a non-null reference.
    /// </param>
    public NotNull(T value)
    {
        UnityEngine.Assertions.Assert.IsFalse(
            object.ReferenceEquals(value, null),
            "Can't create a NotNull from a null value");
        this.value = value;
    }
 
    /// <summary>
    /// Get the wrapped value. Asserts that the returned value is not a null
    /// reference.
    /// </summary>
    /// 
    /// <value>
    /// The wrapped value.
    /// </value>
    public T Value
    {
        get
        {
            UnityEngine.Assertions.Assert.IsFalse(
                object.ReferenceEquals(value, null),
                "Can't get the value of a NotNull with a null value. " +
                "Pass a value to the constructor to create the NotNull.");
            return value;
        }
    }
}

Now let’s look at how to use NotNull<T>:

// Define a function that guarantees to its callers
// that a non-null value will be returned.
//
// Instead of returning just 'string', return 'NotNull<string>'
NotNull<string> GetName()
{
    // Wrap the string in a NotNull<string> and return the wrapper to the caller
    return new NotNull<string>("Jackson");
}
 
// Define a function that requires a non-null value
//
// Instead of passing just 'string', pass 'NotNull<string>'
void PrintNameLength(NotNull<string> name)
{
    // Use the wrapped value
    Debug.Log(name.Value.Length);
}

There are a few ramifications of this design. First, using a NotNull<T> instead of just a T indicates to users of the code that the wrapped value will never be null. In the above example, GetName tells its callers that a non-null name will always be returned. Likewise, PrintNameLength tells its callers that it requires them to pass a non-null value. This is a boon to readability and usability.

Second, using NotNull<T> usually moves the point at which an exception is thrown. Normally, the exception would occur at the point where the null value is used. If PrintNameLength took just a string then its call to the Length property would throw a NullReferenceException if name were null. Instead, whichever code that constructs the NotNull<string> that is eventually passed to PrintNameLength will receive the AssertionException. This helps track down the problem when it happens, not when its consequences eventually occur.

Third, there’s no need to add null checks when using a NotNull<T> because the check is already present when wrapping a value or retrieving the wrapped value. This cleans up the code so readers aren’t distracted by null checks that are unrelated to the overall purpose of the code. Since the null checks are removed from release builds, this also improves performance.

Owner

The next utility from the C++ GSL is owner<T>, translated into the C# naming convention as Owner<T>. This is also a struct that wraps a value. In this case, it doesn’t enforce that the value is not a null reference. It doesn’t even enforce that T is any particular type. This makes it tremendously easy to implement!

/// <summary>
/// Wrapper for an object to indicate that ownership is being transfered. The
/// new owner is responsible for either transfering ownership again or disposing
/// the wrapped value.
/// </summary>
/// 
/// <author>
/// Jackson Dunstan, https://jacksondunstan.com/articles/4953
/// </author>
/// 
/// <license>
/// MIT
/// </license>
public class Owner<T>
{
    /// <summary>
    /// The wrapped value
    /// </summary>
    private readonly T value;
 
    /// <summary>
    /// Wrap a value.
    /// </summary>
    /// 
    /// <param name="value">
    /// Value to wrap
    /// </param>
    public Owner(T value)
    {
        this.value = value;
    }
 
    /// <summary>
    /// Get the wrapped value
    /// </summary>
    /// 
    /// <value>
    /// The wrapped value.
    /// </value>
    public T Value
    {
        get
        {
            return value;
        }
    }
}

So what good is Owner<T> if all it does is wrap a value? Well, it has two related purposes. First, Owner<T> adds the name Owner to parameters and return values. This expresses the intent of the function more clearly than just passing an unwrapped value. For example, if a function takes an IDisposable then should the caller or the function call Dispose on it? The ownership of the object is unclear.

By passing a parameter as an Owner<IDisposable>, it’s clear that the caller is handing off ownership of the wrapped IDisposable to the function. The caller is stating that it will not call Dispose on the parameter. It is now the responsibility of the function to either call Dispose on the parameter or hand off the Owner<IDisposable> object for another function to own.

Likewise, by returning an Owner<IDisposable>, the function is handing off ownership of the wrapped IDisposable to the caller. The function states to the caller that it will not call Dispose on the return value. Normally this is possible because the function may be a method of a class that has that IDisposable as a field. It is the responsibility of the caller to either call Dispose or hand off ownership again.

The second purpose is that this transfer of ownership is explicit. Function signatures are sometimes not read by their callers, but Owner<T> requires the caller to directly acknowledge that ownership is being transfered to or from the function. This reduces the chance of errors by requiring clear, deliberate action in an error-prone section of the code.

Now let’s see Owner<T> in action by looking at an example of passing ownership to a function via a parameter:

byte[] ReadFile(Owner<FileStream> file)
{
    // Read until we have the whole file
    int len = file.Value.Length;
    byte[] bytes = new byte[len];
    while (len > 0)
    {
        len -= file.Value.Read(bytes, bytes.Length-len, len);
    }
 
    // We own the stream, so dispose it
    file.Value.Dispose();
 
    // Return the file's contents
    return bytes;
}
 
// Open the file
FileStream file = File.OpenRead("/path/to/file");
 
// Pass ownership to the function
byte[] contents = ReadFile(new Owner<FileStream>(file));

In this example the ReadFile function is much clearer about its intent to call Dispose on the FileStream than if it simply took a FileStream parameter. By taking an Owner<FileStream>, it tells the caller that it’s taking ownership and forces them to explicitly type new Owner<FileStream> to pass ownership. The caller will not be surprised when ReadFile calls Dispose on their parameter!

Now let’s take a look at an example of a function that hands off ownership to the caller via a return value:

Owner<FileStream> OpenFile(string dir, string fileName)
{
    // Build the full path
    string path = File.Combine(dir, fileName);
 
    // Open the file
    FileStream file = File.OpenRead(path);
 
    // Hand off ownership to the caller
    return new Owner<FileStream>(file);
}
 
// Acquire ownership from the function
Owner<FileStream> file = OpenFile("/path/to/files", "myfile.dat");
 
// Pass ownership to a function
byte[] contents = ReadFile(file);

Here we’ve written OpenFile in a way that clearly states that the caller is responsible for calling Dispose on the returned FileStream. The caller is forced to deal with this fact by declaring their return value as Owner<FileStream> and explicitly extracting the FileStream out of it by typing .Value. It’s very unlikely that the caller will forget that they now own this FileStream and forget to call Dispose on it.

In this particular case, the caller doesn’t use the Value property or call Dispose on it. That’s because they opt to instead transfer ownership of the FileStream by calling a function that takes an Owner<FileStream> parameter. This absolves them of the responsibility to call Dispose because the new owner, ReadFile in this case, is now the owner and therefore must call Dispose.

Conclusion

Today we’ve seen two small examples of utilities from the C++ GSL. Both are easy ways to improve our C# code that imply little or even negative performance overhead. Feel free to incorporate one or both of them into your projects.