How can we use non-nullable reference types in combination with the Options pattern?
Let's say we have an options model named MyOptions
.
The services requiring those options are getting IOptions<MyOptions> options
injected into the constructor.
Configuring the options happens on the IServiceCollection
like this:
services .AddOptions<MyOptions>() .Configure(options => { options.Name = "ABC"; });
Now, the problem is in the definition of MyOptions
:
public sealed class MyOptions { public string Name { get; set; } }
which generates the warning:
CS8618 Non-nullable property 'Name' is uninitialized. Consider declaring the property as nullable.
Name
nullable as then we need to place traditional null checks everywhere (which is against the purpose of non-nullable reference types)MyOptions
class to be created with a non-nullable name
value as the Configure
method construct the options instance for uspublic string name { get; set; } = null!;
) as then we can't ensure the Name
property is set and we can end up with a null
in the Name
property where this would not be expected (inside the services)Any other option I forgot to consider?
Nullable reference types are a compile time feature. That means it's possible for callers to ignore warnings, intentionally use null as an argument to a method expecting a non nullable reference. Library authors should include runtime checks against null argument values.
C# provides a special data types, the nullable types, to which you can assign normal range of values as well as null values. For example, you can store any value from -2,147,483,648 to 2,147,483,647 or null in a Nullable<Int32> variable. Similarly, you can assign true, false, or null in a Nullable<bool> variable.
To enable per file, you can use #nullable enable where you want to enable the functionality and #nullable disable where you want to disable it. This means you can opt-in or opt-out of the nullable reference types where you want. This can be very useful to migrate an existing codebase.
null isn't a type. From msdn: The null keyword is a literal that represents a null reference, one that does not refer to any object. Nullable types are instances of the Nullable<T> struct which "Represents a value type that can be assigned null."
Nullable reference types communicate that design intent clearly: The questions that are part of the survey can never be null: It makes no sense to ask an empty question. The respondents can never be null. You'll want to track people you contacted, even respondents that declined to participate. Any response to a question may be null.
There are four values for the nullable annotation context: Nullable warnings are disabled. All reference type variables are nullable reference types. You can't declare a variable as a nullable reference type using the ? suffix on the type. You can use the null forgiving operator, !, but it has no effect.
A nullable reference type is noted using the same syntax as nullable value types: a ? is appended to the type of the variable. For example, the following variable declaration represents a nullable string variable, name: Any variable where the ? isn't appended to the type name is a non-nullable reference type.
Any variable of a reference type is a non-nullable reference. Any non-nullable reference may be dereferenced safely. Any nullable reference type (noted by ? after the type in the variable declaration) may be null. Static analysis determines if the value is known to be non-null when it's dereferenced.
It seems, that you have two possible options here. First one is to initialize an Options
properties using empty string (instead of null
value) to avoid null
checks
public sealed class MyOptions { public string Name { get; set; } = ""; }
Second one is to make all of the properties a nullable ones and decorate them using DisallowNull
precondition and NotNull
postcondition.
DisallowNull
means that nullable input argument should never be null, NotNull
- a nullable return value will never be null. But these attributes only affect nullable analysis for the callers of members that are annotated with them. So, you are indicating that your property can never return null
or be set to null
, despite nullable declaration
public sealed class MyOptions { [NotNull, DisallowNull]public string? Name { get; set; } }
and the usage example
var options = new MyOptions(); options.Name = null; //warning CS8625: Cannot convert null literal to non-nullable reference type. options.Name = "test";
But the next example doesn't show a warning, because nullable analysis doesn't work properly in object initializers yet, see GitHub issue 40127 in Roslyn repository.
var options = new MyOptions { Name = null }; //no warning
(Edit: This issue was fixed already, shipped in version 16.5 in March, 2020 and should go away after updating a VS to the latest version.)
The same picture for property getter, the following sample doesn't show any warnings, because you indicated that nullable return type can't be null
var options = new MyOptions(); string test = options.Name.ToLower();
but attempting to set a null
value and get it generates a warning (compiler is smart enough to detect such scenarios)
var options = new MyOptions() { Name = null }; string test = options.Name.ToLower(); //warning CS8602: Dereference of a possibly null reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With