Recently after posting my article C# 6 – Null-Conditional Operator an interesting discussion was going on at Reddit. While the overall feedback was very positive, a few argued to never use null.
I’m in the same camp as David Fowler with Software is about making tradeoffs.. You could be strict about always using the null-conditional operator, strict about not using it, or in between to use it when it makes sense. While the previous post explained, the null-conditional operator, in this article I want to discuss null (or Nothing in Visual Basic) in a more general term, and also show what’s in the pipeline for future versions of C#.
NullReferenceException
If you are not a beginner programming .NET, I’m sure you already had your experiences finding issues of NullReferenceException
s. If the exception was not in your code, the reason to find the issue is harder to find. There’s a .NET guideline that your code should never throw exceptions of type NullReferenceException
. One way to deal with this is to allow null. This way you need to deal with this behavior accordingly. However, if the method cannot do anything useful without the received object, it should throw ArgumentNullException
.
The following code snippet throws an ArgumentNullException
if the received Bar
object is null. In case you need the object within the method (the Foo
method in the sample), throw an exception if the method is invoked in a wrong way. This helps the caller immediately when the method is invoked. Otherwise, the exception comes from a method that makes use of the Bar
object, such as the Doit
method:
public void Foo(Bar b) { if (b == null) throw new ArgumentNullException(nameof(b)); Doit(b); }
The nameof keyword available with C# 6 helps dealing with such exceptions. Instead of passing a string, you take the name of the variable. This helps a lot with refactoring.
Often I’m asked why it’s better to throw an ArgumentNullException instead of a NullReferenceException – the program throws in both cases. If you let the program throw a NullReferenceException, the exception can be thrown any hierarchy deep in the stack trace. Often it’s not that easy to find the reason. As soon as you are not a newbie, I’m sure you already had to deal with such issues. Throwing an ArgumentNullException you immediately see the issue when calling the method where you passed an incorrect initalized instance. The error is a lot easier to find.
Allowing null
There are some scenarios where null is a useful value. There’s also a reason database columns can be specified nullable, where other columns require a value. Receiving JSON or XML from a service, often some values are optional. Many methods of the .NET Framework return null in case nothing was found. Let’s get at one example. Part of the example is taken from the Language Integrated Query chapter of my book Professional C# 6 and .NET Core 1.0
The following code snippet throws an exception of type InvalidOperationException
because the predicate passed to the First method doesn’t return any racer. The sequence contains no matching element. Indeed, the Formula1.GetChampions
method returns a list of all Formula 1 world champions. As of today, there’s no champion from Hungary. Hungary has a Formula 1 race, and there was one Formula 1 race driver from Hungary (Zsolt Baumgartner), but his best race position was 8th at the United States GP in the year 2004.
var racers = Formula1.GetChampions(); Racer hungarianRacer = champions.First(r => r.Country == "Hungary");
It’s not a good idea to throw exceptions for complete normal circumstances. If you require a result with your query it’s ok to throw an exception. However, if this could be normal behavior it’s better to use the FirstOrDerfault
method instead of the First
method. FirstOrDefault
doesn’t throw an exception, but returns the default value for a type in case no matching element was found. With a reference type, the default is null.
var racers = Formula1.GetChampions(); Racer hungarianRacer = champions.FirstOrDefault(r => r.Country == "Hungary");
Before using hungarianRacer
you need to check for null.
As the hungarianRacer
variable indeed is null, I’m doing a query for UK racers next. The next result should be the number of race wins from the first UK Formula 1 champion. The Racer
class is defined with a Starts
property that can never be null, and a Wins
property that can be null. This is a simplified example, and of course the Wins
property could be defined to be not nullable as well. However, in a usual entity type you often find properties, and often properties accessing relations to be of reference types, and thus null is a valid value. With the Racer
class, the Country
property could be null as well.
public class Racer { // more members public string Country { get; set; } public int Starts { get; set; } public int? Wins { get; set; } }
A traditional way to deal with this verifying if the received object is not null, such as checking ukRacer, and then to check if the Wins property is not null before accessing the value:
Racer ukRacer = racers.FirstOrDefault(r => r.Country == "UK"); int wins = 0; if (ukRacer != null) { if (ukRacer.Wins != null) { wins = ukRacer.Wins.Value; } }
With code I’m reviewing I often see such examples where not only two if statements are nested. With the previous example it would be possible to combine the two if statements into one – using a logical operator.
Using the C# 6 null-conditional operator, the code can be simplified. If ukRacer
is null, the result of the expression is null, and thus because of the coalescing operator, 0 is assigned to the wins variable. If ukRacer
is not null, the value of the Wins
property is accessed. If the value of the Wins
property is null again, the coalescing operator comes into play. If the value is not null, the value is assigned to the wins
variable:
Racer ukRacer = racers.FirstOrDefault(r => r.Country == "UK"); int wins = ukRacer?.Wins ?? 0;
Of course this can also be simplified into a single statement:
int wins = racers.FirstOrDefault(r => r.Country == "UK")?.Wins ?? 0;
Using the as Operator
It’s best to never do a cast. However, often this is not possible – at least not without a lot of work. Using the as operator, the result needs to be verified if it is not null:
var r = p as Racer; if (r != null) { int wins = r.Wins ?? 0; }
The null-conditional operator becomes very handy:
int wins = (p as Racer)?.Wins ?? 0;
Event Handling
I’ve already shown the event handling sample with the null-conditional operator in a previous blog post, but because it is such a useful C# 6 enhancement dealing with null, I’m repeating int here:
Old code:
public event EventHandler<CarInfoEventArgs> NewCarInfo; private void OnNewCarInfo(string car) { EventHandler<CarInfoEventArgs> handler = NewCarInfo; if (handler != null) { handler(this, new CarInfoEventArgs(car)); } }
New code:
public event EventHandler<CarInfoEventArgs> NewCarInfo; private void OnNewCarInfo(string car) { NewCarInfo?.Invoke(this, new CarInfoEventArgs(car)); }
Nullable Reference Types and Nullability Checking
While it is ok with some members to be null (such as you’ve seen), the values of most reference types should never be null. If C# would be created today, it would not be allowed to assign null to a reference type, unless it is explicitly allowed. With today’s C# implementation, this feature is available with value types. Value types couldn’t be null since the first version of C#. The creation of Nullable
allowed value types to be null. Nullable is a generic type that allows using any value type as the generic parameter, and thus allow assign null to it. Behind the scenes, Nullable
defines a boolean field which defines if the value is null or not. Thus a nullable value doesn’t have the overhead of a reference type.
As Nullable
is such an important feature, C# allows adding ? to the type where the compiler in turn defines a type of Nullable
instead.
int x1 = 3; Nullable<int> x2 = null; int? x3 = null; Point p1 = new Point(10, 20); Point? p2 = null;
Nullable reference types and nullability checking will not be part of the upcoming C# 7, but there’s a good chance for this feature in versions available after the release of C# 7.
This nullability is already in plans for a future version of C#. With the current plans, all the reference types cannot be null – by default – unless the ? is added to the type, such as shown in the following code snippet. With a declaration of string? s2
, the variable s1 is allowed to be null. This is not allowed with the declaration string s1;
Using the s1 variable, null cannot be assigned to.
string s1 = "one"; string? s2 = null; s1 = s2; // compiler warning int length1 = s1.Length; // ok int length2 = s2.Length; // compiler warning int? length3 = s2?.Length; // ok
With generics, the type T now defines types that are not nullable, whereas T? allows nullable types.
Changing the default behavior of course has a big impact of existing libraries. It’s not possible to change all the existing libraries that should be used. That’s why, there’s an opt-in and opt-out mechanism planned using global attributes. Using existing libraries that do not have the new attributes, no compiler warnings will happen, and thus using these libraries works the same as it is today. Re-building a library taking care of the new behavior, an assembly attribute is applied to mark the assembly using the new behavior with nullable reference types. In case it’s too much work to modify the assembly for the new behavior, it’s also possible to opt out using the new compiler.
More information about this proposal is available on GitHub: Nullable reference types and nullability checking.
I can’t wait to try this proposal with some existing code, but it will take some time to have this feature. For sure this feature will not be part of C# 7. With a future version of C#, when this might be implemented, the feature might look different than shown in the proposal. For sure this will be a big change, and having it used with many libraries, it’s easier to deal with null issues.
Summary
Some variables of reference types should never be null, and it’s completely ok with others to be null. Just use it one way or the other where it makes sense. Dealing with null, using the null-conditional operator code becomes less messy and you can have a better focus on the functionality itself. Of course, the null-conditional operator should not be used in every case. Throwing ArgumentNullException
is still an exception that needs to be dealt with.
The C# vfuture proposal on nullable reference types and nullability checking looks very promising, but there’s still some time to come before we can use this.
Have fun with programming and learning!
Christian
More Information
More information about the C# features available with C# 6 in my new book Professional C# 6 and .NET Core 1.0. In my workshops you can already get some information about upcoming features with C# 7 – if you like to – or focus on already shipping features.