A .NET guideline specifies that an application should never throw a NullReferenceException. However, many applications and libraries do. The NullReferenceException is the most common exception happening. That’s why C# 8 tries to get rid of it. With C# 8, reference types are not null be default. This is a big change, and a great feature. However, what about all the legacy code? Can old libraries be used with C# 8 applications, and can C# 7 applications make use of C# 8 libraries?
This article demonstrates how C# 8 allows mixing old and new assemblies.

Why avoiding NullReferenceException?
When a NullReferenceException occurs, the reason often is not easy to find. Errors occur at places far away from the real issue. That’s why applications should not throw NullReferenceException, and instead check for null values, and throw a ArgumentNullException. If a null value is passed with an argument, at method entry it can be checked to not accept the null value. Throwing an ArgumentNullException here, it’s easy to detect where the problem is.
See the guidelines on throwing standard exception types
Let’s see what C# 8 does to avoid the NullReferenceException.
Install C# 8
At the time of this writing, C# 8 is not yet released. However, you can try it out. At the time of this writing, to try nullable reference types, Visual Studio 2017 15.5-15.7 is needed – and the preview of C# Nullable Reference Types. You can easily install it, and uninstall it as described in the linked article.
Installing this version of the compiler, you’ll get many warnings on many of your existing C# projects. By default, the latest major version of the C# compiler is used. To get rid of the warnings, you can explicitly set the C# compiler version to an earlier version with your existing projects, or you can also uninstall the C# 8 compiler again.
Reference Types are not Nullable
The new nullability is easy to understand. The syntax is similar to nullable value types. Similar to value types, if the ? is not specified with the declaration of the type, null is not allowed:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| int i1 = 4; // null is not allowed | |
| int? i2 = null; // null is allowed | |
| string s1 = "a string"; // null is not allowed | |
| string? s2 = null; // a nullable string |
While the syntax with value types and reference types now looks similar, the functionality behind the scenes is very different.
- With value types, the C# compiler makes use of the type
Nullable. This type is a value type and adds a Boolean field to define if the value type is null or not null. - With reference types, the C# compiler adds the Nullable attribute. Version 8 of the compiler knows about this attribute and behaves accordingly. C# 7 and older versions of C# don’t know about this attribute, and just ignore it.
Compiling a program with C# 8, both
Book bandBook? bbecomesBook bwith C# 7.
The following Book class defines the non-nullable properties Title and Publisher, and the nullable property Isbn. In addition to that, this type contains a constructor making use of C# 7 tuples and deconstruction. Using the Book type and accessing the Isbn property, a value can only be written to a variable of type string?. Assigning it to string results in the C# compilation warning converting null literal or possible null value to non-nullable type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Book | |
| { | |
| public string Title { get; } | |
| public string Publisher { get; } | |
| public string? Isbn { get; } | |
| public Book(string title, string publisher, string? isbn) | |
| => (Title, Publisher, Isbn) = (title, publisher, isbn); | |
| public Book(string title, string publisher) | |
| : this(title, publisher, null) { } | |
| public void Deconstruct(out string title, out string publisher, out string? isbn) | |
| => (title, publisher, isbn) = (Title, Publisher, Isbn); | |
| public override string ToString() => Title; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var book = new Book("Professional C# 8", "Wrox Press"); | |
| // string isbn = book.Isbn; // error: converting null literal or possible null value to non-nullable type | |
| string? isbn = book.Isbn; |
Assigning Nullable to Non-Nullable
In case you need to assign a nullable type (like the Isbn property from the Book class), C# 8 analyzes the code. In the code snippet, because the isbn is compared to null, after the if statement, isbn cannot be null anymore. After the if statement it’s ok to return the isbn variable of type string? although the method is declared to return the non-nullable string:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| static string GetIsbn1(Book book) | |
| { | |
| string? isbn = book.Isbn; | |
| if (isbn == null) | |
| { | |
| return string.Empty; | |
| } | |
| return isbn; | |
| } |
Of course, you can also use the coalescing operator instead. This also allows to use a simple Lambda with the implementation of the method:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public string GetIsbn2(Book book) | |
| => book.Isbn ?? string.Empty; |
Returning from and passing to methods
The class NewAndGlory is defined in a class library built with C# 8. The method GetANullString is defined to return a type string?, so null is allowed, and this method just returns null. The method GetAString is defined to return a type of string, so null is not allowed. With the method PassAString, the parameter is defined to receive string. Here, null is not allowed, and there’s no need to verify this.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public class NewAndGlory | |
| { | |
| public string? GetANullString() => null; | |
| public string GetAString() => "a string"; | |
| public string PassAString(string s) => s.ToUpper(); | |
| } |
On the other hand, there’s the library TheOldLib, which makes use of the C# 7.0 compiler. This is defined in the project file TheOldLib.csproj with the element 7. The Legacy class defines the method GetANullString that just returns null, and the method PassAString that receives a string and does the usual testing for null before the string is used. This library also defines the interface ILegacyInterface, which defines a method that returns a string. This string could be meant to be nullable or not, with C# 7 this cannot be specified in the interface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public class Legacy | |
| { | |
| public string GetANullString() => null; | |
| public string PassAString(string s) | |
| { | |
| if (s == null) throw new ArgumentNullException(nameof(s)); | |
| return s.ToUpper(); | |
| } | |
| } | |
| public interface ILegacyInterface | |
| { | |
| string Foo(); | |
| } |
C# 8 Application using Libraries built with C# 7 and C# 8
Now let’s get into the C# 8 Console application that references both the old and the new libraries. Using the class NewAndGlory, as excpected the result from the method GetNullString can only be written to a string? type. Trying to pass null to the method PassAString retsults in the the compilation error cannot convert null literal to non-nullable reference or unconstrained type parameter.
Invoking the Legacy class where the method GetANullString returns null, the result can be written in a string type. Because this library is not implemented with C# 8, the C# 8 compiler doesn’t create a warning. This only happens with new libraries. It’s also possible to invoke the method PassAString and passing null. Using legacy libraries too many errors would be shown, that’s why libraries not built with the new compiler are dealt with differently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var newglory = new NewAndGlory(); | |
| string? s1 = newglory.GetANullString(); | |
| string s2 = newglory.GetAString(); | |
| // string s3 = newglory.PassAString(null); // error: cannot convert null literal to non-nullable reference or unconstrained type parameter | |
| var old = new Legacy(); | |
| string s4 = old.GetANullString(); // no error, s1 is null! | |
| string s5 = old.PassAString(null); // no error |
The method Foo of the interface ILegacyInterface defined in the library built with the C# 7 compiler returns a string. How can this be implemented with C# 8? As you can see in the following code snippet, the interface can be implemented in both ways – returning a nullable string, or a non-nullable string. This is a good way, as with C# 7 it was not possible to declare how this was meant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class SomeClass : ILegacyInterface | |
| { | |
| public string? Foo() => null; | |
| } | |
| class AnotherClass : ILegacyInterface | |
| { | |
| public string Foo() => "a string"; | |
| } |
Interfaces declared with the C# 8 compiler do need the correct implementation in regard to nullability.
C# 7 Application using Libraries built with C# 8
From an old application (an application using C# 7 or earlier), the new C# 8 built library can be used like any other .NET library. The new app doesn’t see nullable types such as string?, and sees string instead – which is nullable for C# 7 anyway. The non-nullable string type becomes a nullable string type. This way, the old application can make use of the new library. Of course, now the new library has the same issues in regard to nullability as the old library:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var glory = new NewAndGlory(); | |
| string s1 = glory.GetANullString(); | |
| string s2 = glory.GetAString(); | |
| string s3 = glory.PassAString(null); // having a NullReferenceException here! |
Invoking the PassAStringMethod and passing null generates a NullReferenceException, when the ToUpper method is invoked on the string. This is the exception we want to avoid, but it still occurs in such an interop scenario. To avoid this, in the C# 8 library we can still check for null and throw a ArgumentNullException which is not required with C# 8 clients. Maybe the next version of the C# 8 compiler automatically creates this implementation with non-nullable types for older C# clients – but it’s only needed for the time when older than C# 8 compilers are used.
Summary
Non-nullable reference types is a new C# functionality that will get rid of many of the NullReferenceException exceptions. This is made possible by changing the default behavior of reference types. Although the default behavior changes, a new C# 8 application can still use old libraries, and a old C# application can make use of new C# 8 libraries. Nullability is implemented by using attributes, which makes this possible.
What do you think about this new C# 8 feature?
Get the complete sample from More Samples!
Before C# 8 is released, read about all the cool C# 7 features in the book Professional C# 7 and .NET Core 2.0!
Enjoy programming and learning,
Christian
If you found this information valuable and want to return me a favor, then buy me a coffee.







Leave a comment