Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullable Reference Types and the Options Pattern

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.

  1. We don't want to make Name nullable as then we need to place traditional null checks everywhere (which is against the purpose of non-nullable reference types)
  2. We can't create a constructor to enforce the MyOptions class to be created with a non-nullable name value as the Configure method construct the options instance for us
  3. We can't use the null-forgiving operator trick (public 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?

like image 714
huysentruitw Avatar asked Sep 24 '19 19:09

huysentruitw


People also ask

What is a nullable reference type?

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.

What are nullable types in C#?

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.

What is #nullable disable?

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.

IS null value a reference type?

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."

What is a nullable reference type?

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.

What are the values for the Nullable annotation Context?

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.

How do you know if a variable is nullable?

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.

What is a non-nullable reference?

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.


Video Answer


1 Answers

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. 
like image 100
Pavel Anikhouski Avatar answered Sep 24 '22 07:09

Pavel Anikhouski