C# 8 introduces the concept of non-nullable reference types which can be
a big help in getting rid of code that throws NullReferenceExcpetion
. While
compiling the code, the compiler generates warnings in a clever way, whenever
it detects a potential unwanted dereference of null
.
string? x = null; // x is a nullable reference to string
string y = "hi"; // y is a non-nullable reference to string
When accessing x
, the compiler will make sure that you have tested that x
is not null befoere letting you call any of its methods, whereas it assumes
that y
being declared as non-nullable won't ever contain null
, and thus
won't require any tests.
var len1 = x.Length; // warning CS8602: Possible dereference of a null reference.
var len2 = y.Length;
y = x; // warning CS8600: Converting null literal or possible null value to non-nullable type.
I've been using ?.
to conditionally access members of possibly null
references for some time now; this operator is fondly known as the Elvis
operator (for obvious reasons).
Now comes the dammit operator, as Mads Torgerson coined it during one of
his talks at NDC London 2019: if you think you know what you are doing,
you can use !
to silence the compiler. Call that method !.
or assign
that value !
– dammit.
var len1 = x!.Length; // no warning as I'm telling the compiler "I know what I do"
var len2 = y.Length;
y = x!; // no warning either
There is a description of the feature in the Roslyn directory, but it seems to be a bit out of sync as of this writing (Feb. 2 2019).
I wanted to know if I could figure out by using reflection, if a property in a
class was being declared as string
or as string?
. Since code compiled with
the C# 8 compiler should still run on an unmodified CLR, I was on the lookout
for a compiler-generated attribute to represent that additional runtime-type
information. And indeed, if you open a DLL compiled with C# 8, you'll see that
now all usage of reference types gets decorated with a NullableAttribute
.
.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 )
Searching for NullableAttribute
on GitHub didn't reveal any new class in
dotnet
(which is no surprise, as this would mean that we'd have to use an
update version of .NET to get the assembly to resolve the attribute). The
only place where I found it, was inside of the Roslyn compiler.
The compiler is in fact emitting a minimal implementation of NullableAttribute
which has two empty constructors:
NullableAttribute(byte a) {}
NullableAttribute(byte[] a) {}
When using reflection (e.g. GetProperties()
and GetCustomAttributes()
I do
see the NullableAttribute
, but I cannot reference the type itself; it is as
if the compiler did not know that it existed before it emitted the code. So in
order to play with it, I provided my own implementation:
namespace System.Runtime.CompilerServices
{
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Event | AttributeTargets.Field |
AttributeTargets.GenericParameter | AttributeTargets.Module | AttributeTargets.Parameter |
AttributeTargets.Property | AttributeTargets.ReturnValue,
AllowMultiple = false)]
public class NullableAttribute : Attribute
{
public byte Mode { get; }
public NullableAttribute(byte mode)
{
this.Mode = mode;
}
public NullableAttribute(byte[] _) => throw new System.NotImplementedException ();
}
}
The compiler stopped emitting its own version and used mine. And now I could see
the difference between string
and string?
. In the first case, the constructor
of NullableAttribute
is fed with 1
. And in the case of a nullable reference
type, it is fed with 2
.
I've put together this project to explore this in more depth. You'll see what happens when mixing reference types and arrays, which can also be nullable or non-nullable.
For now, to get everything working properly, the *.csproj
settings need to be
set like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<NullableContextOptions>enable</NullableContextOptions>
...
</PropertyGroup>
</Project>