I recently released a two-part course on C# 8. The first part is for beginning programmers. The second part is for intermediate to advanced programmers.
This post is the first in a series that focuses on the new features in C# 8.
I recently had Mads Torgenson on my Podcast. Mads is the lead designer of C#. I asked him what the most important new feature of C# is, and he replied (somewhat to my surprise) Nullable Types.
Every one of us has crashed and burned on the dreaded “object reference” exception. Null Reference types are designed to avoid these by expressing which objects might be null and having the editor issue a warning if you create an instance that might be null and try to dereference it.
Let’s look at an example…
We’ll start with a Service that will cough up an array of Person objects:
public static IEnumerable<Person> GetSubscribers()
{
Person[] people =
{
new Person("Martin", "Luther", "King"),
new Person("John", "F", "Kennedy"),
new Person("Miguel", "de Icaza"),
new Person("Mads", "Torgenson")
};
return people.Select( person => person );
Now let’s look at the Person class:
public class Person
{
public Person(string first, string middle, string last)
{
FirstName = first;
MiddleName = middle;
LastName = last;
}
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
In our main method, let’s get the array of subscribers, and then pass that array to a method GetNames. Get names will get the name for each person by calling GetName, which in turn will return the person’s first, middle and last name…
var subscribers = Service.GetSubscribers();
var names = GetNames(subscribers);
...
public IEnumerable<string> GetNames(IEnumerable<Person> people)
{
return people.Select(person => GetName(person));
}
...
public string GetName(Person person)
{
return $"{person.FirstName} {person.MiddleName[0]} {person.LastName}";
This works just fine for the first two names, and then blows up with a null reeference exception when we hit Miguel de Icaza as he does not have a middle name. When we try to take an action on his middle name (dereference it) it is null and we go boom.
The answer to this is to make clear to Visual Studio (and the compiler) what your intent is. If middle name may be null, we signal that with a question mark:
public string FirstName { get; set; }
public string? MiddleName { get; set; }
public string LastName { get; set; }
We’ll also create a second constructor for Person that takes only the first and last name and sets the middle name explicitly to null
public Person(string first, string middle, string last)
{
FirstName = first;
MiddleName = middle;
LastName = last;
}
public Person(string first, string last)
{
FirstName = first;
MiddleName = null;
LastName = last;
}
Now that we’ve signalled our intent, we can change our GetName method to look like this:
return (person.MiddleName != null)
? $"{person.FirstName} {person.MiddleName[0]} {person.LastName}"
: $"{person.FirstName} {person.LastName}";
Opt-In
The designers of C# did not want existing code to suddenly start sprouting warnings because of this new feature, so you must opt-in by placing
#nullable enable
above your namespace, class or method (depending on how large a scope you want to use this with).
Here is my complete source for this sample app…
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace NullableReferenceTypes
{
public static class Program
{
static void Main(string[] args)
{
var runner = new Runner();
runner.Run();
}
}
public class Runner
{
public void Run()
{
var subscribers = Service.GetSubscribers();
var names = GetNames(subscribers);
foreach (var name in names)
{
Console.WriteLine($"{name} has subscribed!");
}
}
public IEnumerable<string> GetNames(IEnumerable<Person> people)
{
return people.Select(person => GetName(person));
}
public string GetName(Person person)
{
return (person.MiddleName != null)
? $"{person.FirstName} {person.MiddleName[0]} {person.LastName}"
: $"{person.FirstName} {person.LastName}";
}
}
public class Person
{
public Person(string first, string middle, string last)
{
FirstName = first;
MiddleName = middle;
LastName = last;
}
public Person(string first, string last)
{
FirstName = first;
MiddleName = null;
LastName = last;
}
public string FirstName { get; set; }
public string? MiddleName { get; set; }
public string LastName { get; set; }
}
public static class Service
{
public static IEnumerable<Person> GetSubscribers()
{
Person[] people =
{
new Person("Martin", "Luther", "King"),
new Person("John", "F", "Kennedy"),
new Person("Miguel", "de Icaza"),
new Person("Mads", "Torgenson")
};
return people.Select( person => person );
}
}
}