Nullable Reference Types Preview

Charles Stoner edited this page Dec 1, 2017 · 34 revisions

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.

The preview installs on top of Visual Studio 2017 15.5. We would love for you to install it and try it out on existing and new code, then tell us what you think!

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.

Installing

To install the preview:

  1. Download and extract Roslyn_Nullable_References_Preview.zip [latest 11/15/17]
  2. Close all instances of Visual Studio
  3. 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 only (Preview 4 or later). Before upgrading Visual Studio to a later version, uninstall the extensions using the uninstall.bat script.

Installing on a machine with multiple versions of Visual Studio is not supported.

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.

The extensions support .NET Framework projects only currently. Support for .NET Core projects should be available soon.

Uninstalling

To uninstall the preview:

  1. Close all instances of Visual Studio
  2. 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

Enabling nullability warnings

The feature design envisions a separate compiler switch to enable warnings related to nullability. In the prototype, the Nullable Reference Types feature is tied to the C# language version and is enabled automatically with the default or latest version of the preview compiler (C# 8.0). To disable the feature use an explicit language version.

csc.exe -langversion:7.2 Legacy.cs

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;
    }
}

Nullability tracking for fields and properties

Flow analysis tracks the nullability within a method body of locals and parameters only. Nullability is not tracked for fields or properties currently.

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 ==        // warning: this.Address may be null
                other.Address.State && // warning: other.Address may be null
            ...;
    }
}

Method type inference

Method type inference uses the declared nullability rather than the nullability inferred from flow analysis.

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

string? str = "";
int n = F(str).Length; // unnecessary warning: maybe null

Array initializers

Array initializers use the declared nullability rather than the nullability inferred from flow analysis.

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

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.

void F(ICollection<string?> arg) { }
F(new List<string>()); // no warning

Deconstruction

Deconstruction drops nullability.

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

Tuple element conversions

Nullability mismatches are not reported for tuple element conversions.

object? x = ...;
string y = ...;
(object, string?) pair = (x, y); // no warning for x

Out var

Out var locals are treated as non-nullable regardless of the corresponding parameter declaration.

void TryGet(out string? s) => s = null;
TryGet(out var t); // string with null assignment warning
int n = t.Length;  // no warning

Conditional operator

Conditional operator uses the nullability of the type arguments from the first operand.

var dA = new Dictionary<string, object?>();
var dB = new Dictionary<string?, object>();
var dC = dA.Count > 0 ? dA : dB; // Dictionary<string, object?>, no warning
dC.Add(null, null); // warning: key is null

Type patterns

Flow analysis does not infer nullability of operand in is Type patterns.

object? obj = F();
if (obj is string str)
{
    obj.ToString(); // unnecessary warning: may be null
}

Type parameters

T? should be reported as an error unless T is constrained to a value type or reference type.

class C<T>
{
    T? F; // should report error: T is unconstrained
}

Frequently asked questions

Warnings from existing assemblies

Q: Why are warnings reported (or not reported) when calling methods in existing assemblies?

string? name = …;
if (string.IsNullOrEmpty(name)) // warning: may be null
    throw Exception();

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

A: Reference types in member signatures in unannotated assemblies are currently treated as non-nullable. As a result, warnings are reported when calling a method with nullable values for parameters that allow null, and warnings are not reported when dereferencing return values that may be null. We expect to change the handling of unannotated assemblies so that reference types are treated as neither explicitly nullable nor non-nullable, with no warnings using such references.

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.

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.