Functional C#: Non-nullable reference types



The topic described in this article is a part of my Applying Functional Principles in C# Pluralsight course.

This is the third article in my Functional C# series.

C# non-nullable reference types: state of affairs

Look at the code example below:

Customer customer = _repository.GetById(id);

Console.WriteLine(customer.Name);

Looks pretty familiar, doesn’t it? But what issues do you see in this code?

The problem here is that we don’t know for sure whether or not GetById method can return null. If there is any chance for it to return null, then we’ll be getting NullReferenceException at run-time. Even worse, there could pass a significant amount of time between getting the customer instance and using it. Therefore, the exception we get will be hard to debug because we won’t be able to easily find out where exactly the customer instance turned to null.

The faster we receive feedback, the less time it takes us to fix the problem. Of course, the quickest feedback possible can only be given by compiler. How cool would it be to just write this code and let the compiler do all the checks required?

Customer! customer = _repository.GetById(id);

Console.WriteLine(customer.Name);

Where Customer! stands for non-nullable type, i.e. a type whose instances can’t turn into null in any way. How cool would it be to be sure that the compiler will tell you if there are any possible code paths that return null?

Yep, very cool. Or even better:

Customer customer = _repository.GetById(id);

Console.WriteLine(customer.Name);

That is, make all the reference types non-nullable by default (just as value types are) and if we want to introduce a nullable type then put it this way:

Customer? customer = _repository.GetById(id);

Console.WriteLine(customer.Name);

Can you imagine a world without all those annoying null reference exceptions? Me neither.

Unfortunately, non-nullable reference types can’t be introduced in C# as a language feature. Such design decisions should be implemented from the day one because otherwise they break almost every existing code base. Check out these articles to get to know more about this topic: Eric Lippert’s article, interesting but probably not realizable design proposal.

But don’t worry. Although we can’t make compiler help us on our way to leverage the power of non-nullable reference types, there still are some workarounds we can resort to. Let’s look at the Customer class we ended up with in the previous post:

public class Customer

{

    public CustomerName Name { get; private set; }

    public Email Email { get; private set; }

 

    public Customer(CustomerName name, Email email)

    {

        if (name == null)

            throw new ArgumentNullException(“name”);

        if (email == null)

            throw new ArgumentNullException(“email”);

 

        Name = name;

        Email = email;

    }

 

    public void ChangeName(CustomerName name)

    {

        if (name == null)

            throw new ArgumentNullException(“name”);

 

        Name = name;

    }

 

    public void ChangeEmail(Email email)

    {

        if (email == null)

            throw new ArgumentNullException(“email”);

 

        Email = email;

    }

}

We moved all the email and customer name validations to separate classes, but we couldn’t do anything with the null checks. As you can see, they are the only checks remaining.

Getting rid of the null checks

So, how can we get rid of them?

By using an IL rewriter, of course! There’s a great NuGet package named NullGuard.Fody built exactly for that purpose: it weaves your assemblies with null checks all over your code base making your classes throw an exception in case a null value comes in as a parameter or comes out as a method result.

To start using it, install the package NullGuard.Fody and mark your assembly with this attribute:

[assembly: NullGuard(ValidationFlags.All)]

From now, every method and property in the assembly automatically gets a null validation check for any input parameter or output value. Our Customer class can now be written as simply as this:

public class Customer

{

    public CustomerName Name { get; private set; }

    public Email Email { get; private set; }

 

    public Customer(CustomerName name, Email email)

    {

        Name = name;

        Email = email;

    }

 

    public void ChangeName(CustomerName name)

    {

        Name = name;

    }

 

    public void ChangeEmail(Email email)

    {

        Email = email;

    }

}

Or even simpler:

public class Customer

{

    public CustomerName Name { get; set; }

    public Email Email { get; set; }

 

    public Customer(CustomerName name, Email email)

    {

        Name = name;

        Email = email;

    }

}

This is what gets compiled under the hood:

public class Customer

{

    private CustomerName _name;

    public CustomerName Name

    {

        get

        {

            CustomerName customerName = _name;

 

            if (customerName == null)

                throw new InvalidOperationException();

 

            return customerName;

        }

        set

        {

            if (value == null)

                throw new ArgumentNullException();

 

            _name = value;

        }

    }

 

    private Email _email;

    public Email Email

    {

        get

        {

            Email email = _email;

 

            if (email == null)

                throw new InvalidOperationException();

 

            return email;

        }

        set

        {

            if (value == null)

                throw new ArgumentNullException();

 

            _email = value;

        }

    }

 

    public Customer(CustomerName name, Email email)

    {

        if (name == null)

            throw new ArgumentNullException(“name”, “[NullGuard] name is null.”);

        if (email == null)

            throw new ArgumentNullException(“email”, “[NullGuard] email is null.”);

 

        Name = name;

        Email = email;

    }

}

As you can see, the validations are equivalent to the ones we wrote manually except that there are also validations for return values which is a good thing.

How to introduce a null value?

So how do we state that a value of some type can be null? We need to use Maybe monad:

public struct Maybe<T>

{

    private readonly T _value;

 

    public T Value

    {

        get

        {

            Contracts.Require(HasValue);

 

            return _value;

        }

    }

 

    public bool HasValue

    {

        get { return _value != null; }

    }

 

    public bool HasNoValue

    {

        get { return !HasValue; }

    }

 

    private Maybe([AllowNull] T value)

    {

        _value = value;

    }

 

    public static implicit operator Maybe<T>([AllowNull] T value)

    {

        return new Maybe<T>(value);

    }

}

As you can see, the input values for the Maybe class are marked with AllowNull attribute. That tells our null guard weaver that it shouldn’t emit null checks for these particular parameters.

With Maybe, we can write the following code:

Maybe<Customer> customer = _repository.GetById(id);

 

And it now becomes obvious that the GetById method can return a null value. From now, we can reason about the code without stepping into it!

Moreover, you now can’t accidentally mess up a nullable value with a non-nullable one, that would lead to a compiler error:

Maybe<Customer> customer = _repository.GetById(id);

ProcessCustomer(customer); // Compiler error

private void ProcessCustomer(Customer customer)

{

    // Method body

}

Of course, you need to consciously decide what assemblies should be weaved. It probably won’t be a good idea to enforce those rules in WFP presentation layer as there are a lot of system components that are inherently nullable. In such environment, null checks just won’t add any value because you can’t do anything with those nulls.

As for domain assemblies, it totally makes sense to introduce such enhancement for them. Moreover, they would benefit from such approach the most.

One little note about Maybe monad. You would probably want to name it Option because of F# language naming conventions. I personally prefer to entitle it Maybe but I would say there is 50/50 distribution between programmers who name it Maybe and the ones who prefer Option name. Of course, it’s just a matter of taste.

What about static checks?

Okay, fast feedback in run-time is good but it is still only a run-time feedback. It would be great if there would be a way to somehow analyze the code statically and provide the feedback even faster, at compile time.

There is such a way: Resharper’s Code Annotations. You can use NotNull attribute to mark method’s parameters and return values as non-nullable. That allows Resharper to raise a warning if you pass a null to a method which parameters are not allowed to be null.

While such approach can be a pretty helpful assistant, it suffers from several problems.

First of all, in order to state that a parameter can’t be null, you should take an action, i.e. mark it with an attribute. It would be better to apply a reversed technique: mark a parameter only if you want it to be nullable. In other words, make non-nullability default and opt-out parameters if necessary just as we do with NullGuard.

Secondly, warning is only a warning. Of course, we could set up “warning as an error” settings in Visual Studio, but still, use of Maybe monad leaves much less wiggle room for potential bugs as it prevents us from illegal use of non-nullable types.

That’s why, although Code Annotations are very useful in some cases, I personally tend to not using them.

Summary

Approach described above is a really powerful one.

  • It helps to reduce the amount of bugs by providing fast feedback if a null value unexpectedly sneaks in.
  • It significantly increases code readability. You don’t need to step into the method to find out whether or not it can return a null value.
  • The null checks are there by default meaning that all of your methods and properties are null-safe unless you specify otherwise. It makes code much cleaner as you don’t need to specify NotNull attributes all over your code base.

Next time, we’ll discuss how to handle exceptions in a functional way. Stay tuned.

Other articles in the series

Share




  • Paulo Zemek

    “Can you imagine a world without all those annoying null reference exceptions? Me neither.”

    Well, I actually can. I proposed something pretty similar when I presented “Constrained C#”. I really understand it is a breaking change to add such a feature in C# but I also think that jumping to F# is not necessarily an answer, so I believe it is arriving the moment to create a “new” language.

    My main concern with the Maybe solution (as I didn’t test) is: What happens if I do:

    Maybe maybe = default(Maybe);
    string x = maybe.Value;

    Do I get a compile-time error (with is OK) or do I get a run-time exception, so I only replaced a NullReferenceException with a different exception? I understand that the code is explicit about the possibility of not having a value, yet if the exception is at run-time, I can’t see it as a big improvement as many developers would simply keep doing: variable.Value.SomethingElse() without first testing if the variable has a value or not.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thank you for the comment, you raised an important issue!

      No, you won’t get a compile-time error. Unfortunately, it’s impossible to get it without changing the language itself. Indeed, one can continue writing variable.Value.SomethingElse() and get a run-time exception instead of a compile-time error.

      But I think it’s not really the case here. Programming languages can’t preserve you from writing invalid conversions that fail in run-time. What they can do is warn you about possible consequences. For example, Spec# ( http://research.microsoft.com/en-us/projects/specsharp/ ), which fully supports non-nullable reference types, does allow you to write the following code:

      string? nullableString = null;
      string nonNullableString = (string)nullableString;

      And this code results in a run-time exception. What they do do is they prevent you from writing code like this, because nullable string and non-nullable string are completely different types:

      string? nullableString = null;
      string nonNullableString = nullableString;

      The Maybe solution preserves this behavior: you can’t just write:

      Maybe nullableString = null;
      string nonNullableString = nullableString;

      Thus, it keeps the semantics of “non-nullability by default” (with the weaver help) enforcing you to use Maybe struct in the cases where you need to introduce a nullable variable.

      The main drawback here (and you mentioned in your comment) is that this enforcement takes place at run-time, not at compile-time.

      >”Do I get a compile-time error (with is OK) or do I get a run-time exception, so I only replaced a NullReferenceException with a different exception?”

      There’s actually nothing wrong with the NullReferenceException itself (besides the fact that it’s a run-time error, not a compile-time one). The problem is that it tends to appear not at the place where a variable turned to null but much further thus complicating debugging.

      Weaver allows you to bring the exception right to the line containing the bug. What we do here is we adhere the Fail-Fast principle as much as possible.

      >”I proposed something pretty similar when I presented “Constrained C#””

      Do you have a link? I would be interesting to read about it.

      • Paulo Zemek

        The link is here:
        http://blogs.msdn.com/b/paulozemek/archive/2014/11/26/constrained-csharp.aspx

        Actually I even started a test project to create a constrained C# interpreter. I don’t know if I will ever finish it, but at this moment I can only write a method body that works with external methods, but it validates things like:

        string? str = MaybeGetString();
        int length = str.Length;
        // compile time error (parse time, as I don’t really compile).

        Yet, it works OK in this case:
        string? str = MaybeGetString();
        ifset (str)
        {
        int length = str.Length;
        }

        At this moment I really need to use ifset as I don’t evaluate the contents of the if to see it there’s a comparison to null or not.
        Actually, the ifset can check many variables at once, so:
        ifset(a, b, c)
        {
        // I can use a, b and c knowing they are not null.
        }
        else
        {
        // here a, b and c are possibly null.
        }

  • MichaelLPerry

    You *could* prove that you don’t dereference a null Maybe if you removed the Value property. You can replace it with an IfNotNull method that takes a callback.
    Maybe maybe = default(Maybe);
    maybe.IfNotNull(v => Console.WriteLine(v));

    On top of that, to be a proper “monad”, you need some kind of “bind” operator. This would forward the execution and return another monad. You could use the query version of IfNotNull as the bind:
    Maybe lowerName = ReadName()
    .IfNotNull(v => v.ToLower());

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Yep, it my projects, I actually have extension methods for “bind” and “map” that work on top of Maybe, that’s why I call Maybe a monad. I didn’t place those extension methods here because there’s no need in them in this concrete example (YAGNI 🙂 ).

      Working with Maybe using bind and map operations is a good thing, but – again – I think in many cases it may signalize about problems with encapsulation (Tell Don’t Ask http://martinfowler.com/bliki/TellDontAsk.html )

  • http://rpajak.com/ Pellared

    The Maybe implicit conversion does not work with interfaces!

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Yeah, that’s actually their essential trait. There’s a workaround that works in some cases, thought:

      MyClass myClass = new MyClass();
      Maybe myInterface = myClass;

  • Ilya Tsilikov

    Non-nullable reference types IMHO are the best benefit of extended C# that’s used for Singularity OS. It’s would be perfect if non-nullable reference types will appear in common .Net version C#.

  • Michael G.

    Good article, although I am not a fan of NullGuard and similar libraries.

    Most third party libraries introduced to a project perform some task that you would otherwise have to code yourself, thus saving time during the development process.

    It is rare that there will be any issues in version differences
    between your own code, and the third party library. It doesn’t matter
    that the third party run on C# 4.0 while the part of your code that
    makes use of it runs on C# 6. It will still work.

    If a third party library is discontinued, the functionality it provides can, with relative ease, be recreated in a custom library with little impact other than the development time required.

    This makes it possible to upgrade the codebase to newer versions of C#, and still retain all critical functionality, with only minor refactoring needed.

    However, in the case of NullGuard, or other similar third party libraries, you will be locked to the version of C# that they support. If they are discontinued, you are faced with either not upgrading your codebase to a newer version of C#, or manually reintroducing all the null-checks that those libraries injected for you.

    Usually it is a bad idea not to upgrade to a newer version of C#, since each version bring performance gains, and quality-of-life improvements that are baked into the language.

    That leaves only the option of manually reintroducing null-checks all over the codebase, which can easily be in several hundred places. All you can really do is cross your fingers and hope that you have covered each and every one of them. If not, at some point, your application will crash.

    The Maybe type is a great addition as it relies solely on the core of the language. The NullGuard, and similar tools, not so much, for the reasons stated above.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      You are raising very good points. This would indeed be a disaster if you’d need to introduce null-checks manually all over the code base. However, NullGuard isn’t tied to a particular version of C#, it works with IL directly. So, unless a future C# version drops backward compatibility on the binary level (which never happened so far), we should be good.

      • Michael G.

        Ah, I see. Then the potential for disaster is not great. Thank you for the quick answer.

  • http://trydent.io TryIO

    I read your prev post of the series (about Primitive Obsession) and I’m wondering if something like this is right (sorry I’m going to write in Java for the sake of speed, mine I mean 😉 ):

    public class FirstName {
    private final String value;

    private FirstName(final String value) { this.value = value; }

    public final String value() { return this.value; }

    public static Optional of(final String name) {
    return Optional.ofNullable(name)
    .filter(name -> name.length() > 3)
    .map(name -> {
    this.value = name;
    return this.value;
    })
    .orElseThrow(() -> new IllegalArgumentException(…));
    }
    }

    Optional in Java8 (or in the lib Guava) is the same thing as your Maybe. What do you think about such implementation?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Looks good overall. One thing looks unclear, thought: why do you throw after using `map`? The return type (Optional) already indicates there might not be a corresponding first name for the string you pass in.

      • http://trydent.io TryIO

        Well it was to be compliant on what you proposed in your prev post «Primitive Obsession» where you throw an ArgumentException: an exception will be thrown because of a validation fail and then an empty optional will be retrieved. Nothing stops and thanks to the Optional monad we can do whatever we want in a validated context, it should be fine.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          Ah, I see. The ArgumentException from the previous post was to show that the validation logic can be scattered across the code base. I would recommend to use either exceptions or return values but not both in a single context.

          As far as I understand, `orElseThrow` is good for situations where you want to show that there’s no way an Optional might not have a value. I would use it as an assertion, to make sure your assumptions about the value in the Optional are correct. In the example above, you can’t make such an assertion as the return value can be empty at some point.

          • http://trydent.io TryIO

            Yes you’re right, it’s better and cleaner in the following way:


            public static Optional of(final String name) {
            return Optional.ofNullable(name)
            .filter(name -> name.length() > 3)
            .map(FirstName::new);
            }

            FirstName.of("Bruce").isPresent(); // yes, since is Bruce
            FirstName.of("Tim").isPresent(); // no, since Tim is <= 3 and the value bound in the optional returned is null
            FirstName.of(null).isPresent(); // no, since null

            Thanks for clear me this!

  • Ulrich

    For ReSharper users, there exists the “Implicit Nullability” extension which brings “not null by default” semantics into ReSharper’s static nullability analysis. It also brings the static analysis in sync with Fody NullGuard.

    => https://github.com/ulrichb/ImplicitNullability#readme

  • Anders Baumann

    Hi Vladimir.

    Have you looked at this library: https://github.com/louthy/language-ext? I like their implementation of Option and Either. In their version the value member is completely encapsulated so there is no way to get a NullPointerException. As they write: ” You won’t see a .Value or a GetValueOrDefault() anywhere in this library. It is because .Value puts us right back to where we started, you may as well not use Option in that case.”

    Thanks,
    Anders

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Hey Anders,

      Yes, I’m familiar with this library. It’s a good one, brings a great F#-ish experience to C#.

      I agree that, to make your code as functional as possible, calls to .Value should be avoided and piping of the whole Option/Maybe should be used instead. This, however, is not always possible. Also, long piping sequences might hinder your ability to spot a problem when something goes wrong (for example, when you try to analyze an exception stack trace in a log file). I personally don’t have a preferred style here and apply the two approaches on a case-by-case basis. If some piece of code makes more sense with .Value, I use it. All things being equal (and not too complex) – I too prefer piping.