63 Nullable Reference Types Preview
Julien Couvreur редактировал(а) эту страницу 2018-11-26 12:53:00 -08:00

The C# Nullable Reference Types Preview

Welcome to the preview implementation of C# Nullable Reference Types!

Nullable reference types are a feature currently planned for C# 8.0. It is introduced in this post on the .NET Blog. It is specified in more details in this feature speclet.

The preview installs on top of Visual Studio 2017 15.5 - 15.8. There is no installer for 15.9, as the first preview of Visual Studio 2019 should be forthcoming.

You can also try the C# Nullable Reference Types preview on SharpLab.

Installation instructions

Please note: The preview is pre-release software. The features and behavior may change without notice. Please use for evaluation purposes only.

Please read all instructions before installing.

Known installation issue

The latest preview which we just shared (mid-September) does not install on 15.8.4 or 15.8.5. This was fixed in 15.8.6.

Installing

To install the preview:

  1. Download and extract Roslyn_Nullable_References_Preview.zip [latest 09/11/18]
  2. Close all instances of Visual Studio
  3. From cmd.exe, run .\install.bat from the zip root

The script will install experimental versions of the Roslyn extensions for Visual Studio, replacing the existing Roslyn extensions. The extensions will be installed to the default hive of Visual Studio.

The extensions are supported on Visual Studio 15.5, 15.6, 15.7, and 15.8 only. Before upgrading Visual Studio to a later version, uninstall the extensions using the uninstall.bat script.

Installing or uninstalling directly from Visual Studio (or double-clicking on the individual .vsix files) is not supported. There are dependencies between the extensions and the scripts are necessary to ensure the extensions are installed and uninstalled in order.

To activate the feature on a project you should add [module: System.Runtime.CompilerServices.NonNullTypes] in any of the source files. Individual types can be marked with [NonNullTypes] to only get non-null reference types and corresponding warnings in those types. You can also use [NonNullTypes(false)] to turn the feature off in parts of your program.

Uninstalling

To uninstall the preview:

  1. Close all instances of Visual Studio
  2. From cmd.exe, run .\uninstall.bat from the zip root

The script will uninstall the experimental versions of the Roslyn extensions, restoring the original Roslyn extensions.

If uninstall fails uninstalling a particular extension, re-run uninstall.exe. In some cases, it may be necessary to run uninstall.exe several times to complete all extensions.

Feedback

Please write up your thoughts and send them to us at this Microsoft email address: nullable@microsoft.com.

We use email so that your feedback is a conversation between you and us at Microsoft. Community discussion is great, but can happen elsewhere. We don't want to distract from understanding what you like and don't like about this feature, and what changes you'd like to see. It is very likely that we will reply with follow-up questions.

Known issues

Some of the most salient issues are described below. See the complete list for more information.

is pattern incorrectly affects nullability

Debug.Assert(x != null); if (!(x is Type)) { x.ToString(); /* incorrect warning */ }

Microsoft.CodeAnalysis.Compilers NuGet package conflict

Referencing a Microsoft.CodeAnalysis.Compilers NuGet package directly in the project is not supported and may result in build errors reported for the .targets file.

IntelliSense display

Intellisense displays the declared nullability rather than the nullability inferred from flow analysis.

intellisensedeclarednullability

Uninitialized fields warning

Uninitialized fields with non-nullable reference types are only reported for constructors that do not call this().

class Person
{
    public string FirstName;
    public string? MiddleName;
    public string LastName;

    // warning: FirstName, LastName uninitialized
    Person() 
    {
    }

    // no warning for FirstName
    Person(string lastName) : this()
    {
        LastName = lastName;
    }
}

Array covariance

Nullability mismatches are reported for array conversions that are co-variant with respect to nullability.

string?[] array = new string[] { "Hello", "World!" }; // unnecessary warning

Conversion to interfaces and base classes

Nullability mismatches of type arguments are not reported for conversions to interfaces or base classes in all cases.

IList<string> x = new List<string?>(); // no warning

Deconstruction

Deconstruction drops nullability.

(string?, string) tuple = (null, "");
var (x, y) = tuple; // (string, string)
x.ToString();       // no warning

Type patterns

Flow analysis does not infer nullability of operand in the case patterns of a switch.

object? obj = F();
switch (obj)
{
    case string s:
        obj.ToString(); // unnecessary warning: may be null
        break;
}

Frequently asked questions

Inferring null state from string.IsNullOrEmpty

Q: Can the compiler infer null state based on the return value of method such as string.IsNullOrEmpty?

return string.IsNullOrEmpty(name) ?
    0 :
    name.Length; // warning: may be null

There is a similar issue writing a method such as bool TryGetValue(TKey key, out TValue? value). The caller is forced to check value before derefencing, even if TryGetValue returns true.

A: We are investigating allowing annotations on methods that describe simple relationships between parameters and return value with respect to null values. The compiler could use those annotations when analyzing the calling code.

In the short-term, a few common methods such as IsNullOrEmpty and Debug.Assert are specially recognized by the compiler to simulate this behavior.

Warnings for initialized fields

Q: Why are warnings reported for fields that are initialized indirectly by the constructor, or outside the constructor?

A: The compiler recognizes fields assigned explicitly in the current constructor only, and warns for other fields declared as non-nullable. That ignores other ways fields may be initialized such as factory methods, helper methods, property setters, and object initializers. We will investigate recognizing common initialization patterns to avoid unnecessary warnings.

Changes from 05/14/18 build

[05/14/18]

Turning the feature on

The feature is no longer on by default. To better reflect the design of feature as we intend to ship it, it is necessary to add the [System.Runtime.CompilerServices.NonNullTypes] attribute in the user code to activate the feature and the new warnings. Without the attribute, warnings are produced if you use the ? annotation on a reference type, or the suppression operator (!). The attribute can be added on the module ([module: NonNullTypes]) as well as on types. This attribute is synthesized by the compiler, so it doesn't need to be declared or referenced.

Nullability in constraints

In a NonNullTypes(true) context, the class constraint now means the type parameter is a non-nullable reference type. If you want to relax this constraint, you can use the new class? constraint. Nullability of constraints is now enforced (although not in declarations yet). So [NonNullTypes] class C<T> where T : class { } cannot be used in executable code such as C<string?> local = new C<string?>();.

T?

The ? annotation can only be used on a type parameter that is known to be a value type (via struct constraint) or a reference type (via class constraint or a non-nullable reference type constraint, such as where T : Base).

Annotation attributes

The following attributes are recognized and affect nullability analysis:

  • [AssertsTrue] (illustrated by Debug.Assert)
  • [AssertsFalse] (illustrated by Assert.False in test frameworks)
  • [EnsuresNotNull] (for a parameter of a method that throws if that parameter is null)
  • [NotNullWhenTrue] (useful for the out parameter in TryGetValue methods)
  • [NotNullWhenFalse] (illustrated by string.IsNullOrEmpty)
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class AssertsTrueAttribute : Attribute
    {
        public AssertsTrueAttribute () { }
    }
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class AssertsFalseAttribute : Attribute
    {
        public AssertsFalseAttribute () { }
    }
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class EnsuresNotNullAttribute : Attribute
    {
        public EnsuresNotNullAttribute() { }
    }
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class NotNullWhenFalseAttribute : Attribute
    {
        public NotNullWhenFalseAttribute() { }
    }
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class NotNullWhenTrueAttribute : Attribute
    {
        public NotNullWhenTrueAttribute() { }
    }
}

Changes from 03/06/18 build

[03/06/18]

Legacy warnings

Assignments of nullable values to non-nullable locals and explicit casts of nullable values to non-nullable types are reported with a single specific error CS8600, to allow disabling warnings for these cases particularly in legacy code. Disabling these warnings does not affect flow analysis.

#pragma warning disable 8600
object o = MaybeNull(); // [CS8600 warning]
string s = (string)o;   // [CS8600 warning]
int n = s.Length;       // warning: may be null

Tracking non-nullable variables

Nullability of locals, parameters, fields, and properties are tracked even if declared non-nullable.

string s = MaybeNull();
s.ToString(); // warning: may be null

Method type inference

Method type inference uses the nullability inferred from flow analysis.

T F<T>(T t) => t;

string? str = ...;
int n = F(str).Length; // warning: F<string?>() maybe null
if (str == null) return;
n = F(str).Length; // no warning for F<string>()

Array initializers

Array initializers use the nullability inferred from flow analysis.

string? str = "";
var array = new[] { str }; // string[] not string?[]
int n = array[0].Length;   // no warning

Conditional operator

Conditional operator considers top-level and nested nullability of operands.

Changes from 11/15/17 build

[11/15/17]

Warnings from existing assemblies

Reference types in member signatures from unannotated assemblies are treated as neither explicitly nullable nor non-nullable, with no warnings from using such references.

string? name = ...;
if (string.IsNullOrEmpty(name)) // no warning
    throw Exception();

string[] args = ...;
string? arg = args.FirstOrDefault();
int n = arg.Length; // no warning

Nullability tracking for fields and properties

Flow analysis tracks the nullability within a method body of fields and properties in addition to locals and parameters.

class Person
{
    ...
    public Address? Address;

    bool HasSameAddress(Person other)
    {
        if (Address == null) return other.Address == null;
        if (other.Address == null) return false;
        return Address.State ==        // no warning
                other.Address.State && // no warning
            ...;
    }
}