Null-Conditional Operator in C# 6.0

Even the newest .NET developers are likely familiar with the NullReferenceException. This is an exception that almost always indicates a bug because the developer didn’t perform sufficient null checking before invoking a member on a (null) object. Consider this example:

public static string Truncate(string value, int length)
{
  string result = value;
  if (value != null) // Skip empty string check for elucidation
  {
    result = value.Substring(0, Math.Min(value.Length, length));
  }
  return result;
}

If it wasn’t for the check for null, the method would throw a NullReferenceException. Although it’s simple, having to check the string parameter for null is somewhat verbose. Often, that verbose approach is likely unnecessary given the frequency of the comparison. C# 6.0 includes a new null-conditional operator that helps you write these checks more succinctly:

public static string Truncate(string value, int length)
{          
  return value?.Substring(0, Math.Min(value.Length, length));
}
[TestMethod]
public void Truncate_WithNull_ReturnsNull()
{
  Assert.AreEqual(null, Truncate(null, 42));
}

As the Truncate_WithNull_ReturnsNull method demonstrates, if in fact the value of the object is null, the null-conditional operator will return null. This begs the question of what happens when the null-conditional operator appears within a call chain, as in the next example:

public static string AdjustWidth(string value, int length)
{
  return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
}
[TestMethod]
public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
{
  Assert.AreEqual(42, AdjustWidth("Inigo Montoya", 42).Length);
}

Even though Substring is called via the null-conditional operator, and a null value?.Substring could seemingly return null, the language behavior does what you would want. It short-circuits the call to PadRight, and immediately returns null, avoiding the programming error that would otherwise result in a NullReferenceException. This is a concept known as null-propagation.

The null-conditional operator conditionally checks for null before invoking the target method and any additional method within the call chain. Potentially, this could yield a surprising result such as in the statement text?.Length.GetType.

If the null-conditional returns null when the invocation target is null, what’s the resulting data type for an invocation of a member that returns a value type—given a value type can’t be null? For example, the data type returned from value?. Length can’t simply be int. The answer, of course, is a nullable (int?). In fact, an attempt to assign the result simply to an int will produce a compile error:

int length = text?.Length; // Compile Error: Cannot implicitly convert type 'int?' to 'int'

The null-conditional has two syntax forms. First, is the question mark prior to the dot operator (?.). The second is to use the question mark in combination with the index operator. For example, given a collection, instead of checking for null explicitly before indexing into the collection, you can do so using the null conditional operator:

public static IEnumerable GetValueTypeItems(
  IList collection, params int[] indexes)
  where T : struct
{
  foreach (int index in indexes)
  {
    T? item = collection?[index];
    if (item != null) yield return (T)item;
  }
}

This example uses the null-conditional index form of the operator ?[…], causing indexing into collection only to occur if collection isn’t null. With this form of the null-conditional operator, the T? item = collection?[index] statement is behaviorally equivalent to:

T? item = (collection != null) ? collection[index] : null.

Note that the null-conditional operator can only retrieve items. It won’t work to assign an item. What would that mean, given a null collection, anyway?

Note the implicit ambiguity when using ?[…] on a reference type. Because reference types can be null, a null result from the ?[…] operator is ambiguous about whether the collection was null or the element itself was, in fact, null.

One particularly useful application of the null-conditional operator resolves an idiosyncrasy of C# that has existed since C# 1.0—checking for null before invoking a delegate. Consider the C# 2.0 code below:

class Theremostat
{
  event EventHandler OnTemperatureChanged;
  private int _Temperature;
  public int Temperature
  {
    get
    {
      return _Temperature;
    }
    set
    {
      // If there are any subscribers, then
      // notify them of changes in temperature
      EventHandler localOnChanged =
        OnTemperatureChanged;
      if (localOnChanged != null)
      {
        _Temperature = value;
        // Call subscribers
        localOnChanged(this, value);
      }
    }
  }
}

Leveraging the null-conditional operator, the entire set implementation is reduced to simply:

OnTemperatureChanged?.Invoke(this, value)

All you need now is a call to Invoke prefixed by a null-conditional operator. You no longer need to assign the delegate instance to a local variable in order to be thread safe or even to explicitly check the value for null before invoking the delegate.

C# developers have wondered if this would be improved for the last four releases. It’s finally going to happen. This feature alone will change the way you invoke delegates.

Another common pattern where the null-conditional operator could be prevalent is in combination with the coalesce operator. Instead of checking for null on linesOfCode before invoking Length, you can write an item count algorithm as follows:

List linesOfCode = ParseSourceCodeFile("Program.cs");
return linesOfCode?.Count ?? 0;

In this case, any empty collection (no items) and a null collection are both normalized to return the same count. In summary, the null-conditional operator will:

  • Return null if the operand is null
  • Short-circuit additional invocations in the call chain if the operand is null
  • Return a nullable type (System.Nullable) if the target member returns a value type
  • Support delegate invocation in a thread safe manner
  • Is available as both a member operator (?.) and an index operator (?[…])

Leave a Reply

Your email address will not be published.