Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft EdgeMore info about Internet Explorer and Microsoft Edge
Table of contentsExit focus mode

Nullable reference types

  • 2024-12-14
Feedback

In this article

Nullable reference types are a group of features that minimize the likelihood that your code causes the runtime to throwSystem.NullReferenceException. Three features that help you avoid these exceptions, including the ability to explicitly mark a reference type asnullable:

  • Improved static flow analysis that determines if a variable might benull before dereferencing it.
  • Attributes that annotate APIs so that the flow analysis determinesnull-state.
  • Variable annotations that developers use to explicitly declare the intendednull-state for a variable.

The compiler tracks thenull-state of every expression in your code at compile time. Thenull-state has one of two values:

  • not-null: The expression is known to be not-null.
  • maybe-null: The expression might benull.

Variable annotations determine thenullability of a reference type variable:

  • non-nullable: If you assign anull value or amaybe-null expression to the variable, the compiler issues a warning. Variables that arenon-nullable have a default null-state ofnot-null.
  • nullable: You can assign anull value or amaybe-null expression to the variable. When the variable's null-state ismaybe-null, the compiler issues a warning if you dereference the variable. The default null-state for the variable ismaybe-null.

The rest of this article describes how those three feature areas work to produce warnings when your code mightdereference anull value. Dereferencing a variable means to access one of its members using the. (dot) operator, as shown in the following example:

string message = "Hello, World!";int length = message.Length; // dereferencing "message"

When you dereference a variable whose value isnull, the runtime throws aSystem.NullReferenceException.

Similarly warnings can be produced when[] notation is used to access a member of an object when the object isnull:

using System;public class Collection<T>{    private T[] array = new T[100];    public T this[int index]    {        get => array[index];        set => array[index] = value;    }}public static void Main(){    Collection<int> c = default;    c[10] = 1;    // CS8602: Possible dereference of null}

You'll learn about:

  • The compiler'snull-state analysis: how the compiler determines if an expression is not-null, or maybe-null.
  • Attributes that are applied to APIs that provide more context for the compiler's null-state analysis.
  • Nullable variable annotations that provide information about your intent for variables. Annotations are useful for fields, parameters, and return values to set the default null-state.
  • The rules governinggeneric type arguments. New constraints were added because type parameters can be reference types or value types. The? suffix is implemented differently for nullable value types and nullable reference types.
  • TheNullable context help you migrate large projects. You can enable warnings and annotations in the nullable context in parts of your app as you migrate. After you address more warnings, you can enable both settings for the entire project.

Finally, you learn known pitfalls for null-state analysis instruct types and arrays.

You can also explore these concepts in our Learn module onNullable safety in C#.

Null-state analysis

Null-state analysis tracks thenull-state of references. An expression is eithernot-null ormaybe-null. The compiler determines that a variable isnot-null in two ways:

  1. The variable was assigned a value that is known to benot-null.
  2. The variable was checked againstnull and wasn't assigned since that check.

Any variable that the compiler can't determined asnot-null is consideredmaybe-null. The analysis provides warnings in situations where you might accidentally dereference anull value. The compiler produces warnings based on thenull-state.

  • When a variable isnot-null, that variable can be dereferenced safely.
  • When a variable ismaybe-null, that variable must be checked to ensure that it isn'tnull before dereferencing it.

Consider the following example:

string? message = null;// warning: dereference null.Console.WriteLine($"The length of the message is {message.Length}");var originalMessage = message;message = "Hello, World!";// No warning. Analysis determined "message" is not-null.Console.WriteLine($"The length of the message is {message.Length}");// warning!Console.WriteLine(originalMessage.Length);

In the preceding example, the compiler determines thatmessage ismaybe-null when the first message is printed. There's no warning for the second message. The final line of code produces a warning becauseoriginalMessage might be null. The following example shows a more practical use for traversing a tree of nodes to the root, processing each node during the traversal:

void FindRoot(Node node, Action<Node> processNode){    for (var current = node; current != null; current = current.Parent)    {        processNode(current);    }}

The previous code doesn't generate any warnings for dereferencing the variablecurrent. Static analysis determines thatcurrent is never dereferenced when it'smaybe-null. The variablecurrent is checked againstnull beforecurrent.Parent is accessed, and before passingcurrent to theProcessNode action. The previous examples show how the compiler determinesnull-state for local variables when initialized, assigned, or compared tonull.

The null-state analysis doesn't trace into called methods. As a result, fields initialized in a common helper method called by all constructors might generate a warning with the following message:

Non-nullable property 'name' must contain a non-null value when exiting constructor.

You can address these warnings in one of two ways:Constructor chaining, ornullable attributes on the helper method. The following code shows an example of each. ThePerson class uses a common constructor called by all other constructors. TheStudent class has a helper method annotated with theSystem.Diagnostics.CodeAnalysis.MemberNotNullAttribute attribute:

using System.Diagnostics.CodeAnalysis;public class Person{    public string FirstName { get; set; }    public string LastName { get; set; }    public Person(string firstName, string lastName)    {        FirstName = firstName;        LastName = lastName;    }    public Person() : this("John", "Doe") { }}public class Student : Person{    public string Major { get; set; }    public Student(string firstName, string lastName, string major)        : base(firstName, lastName)    {        SetMajor(major);    }    public Student(string firstName, string lastName) :        base(firstName, lastName)    {        SetMajor();    }    public Student()    {        SetMajor();    }    [MemberNotNull(nameof(Major))]    private void SetMajor(string? major = default)    {        Major = major ?? "Undeclared";    }}

Nullable state analysis and the warnings the compiler generates help you avoid program errors by dereferencingnull. The article onresolving nullable warnings provides techniques for correcting the warnings most likely seen in your code. The diagnostics produced from null state analysis are warnings only.

Attributes on API signatures

The null-state analysis needs hints from developers to understand the semantics of APIs. Some APIs provide null checks, and should change thenull-state of a variable frommaybe-null tonot-null. Other APIs return expressions that arenot-null ormaybe-null depending on thenull-state of the input arguments. For example, consider the following code that displays a message in upper case:

void PrintMessageUpper(string? message){    if (!IsNull(message))    {        Console.WriteLine($"{DateTime.Now}: {message.ToUpper()}");    }}bool IsNull(string? s) => s == null;

Based on inspection, any developer would consider this code safe, and shouldn't generate warnings. However the compiler doesn't know thatIsNull provides a null check and issues a warning for themessage.ToUpper() statement, consideringmessage to be amaybe-null variable. Use theNotNullWhen attribute to fix this warning:

bool IsNull([NotNullWhen(false)] string? s) => s == null;

This attribute informs the compiler, that, ifIsNull returnsfalse, the parameters isn't null. The compiler changes thenull-state ofmessage tonot-null inside theif (!IsNull(message)) {...} block. No warnings are issued.

Attributes provide detailed information about the null-state of arguments, return values, and members of the object instance used to invoke a member. The details on each attribute can be found in the language reference article onnullable reference attributes. As of .NET 5, all .NET runtime APIs are annotated. You improve the static analysis by annotating your APIs to provide semantic information about thenull-state of arguments and return values.

Nullable variable annotations

Thenull-state analysis provides robust analysis for local variables. The compiler needs more information from you for member variables. The compiler needs more information to set thenull-state of all fields at the opening bracket of a member. Any of the accessible constructors could be used to initialize the object. If a member field might ever be set tonull, the compiler must assume itsnull-state ismaybe-null at the start of each method.

You use annotations that can declare whether a variable is anullable reference type or anon-nullable reference type. These annotations make important statements about thenull-state for variables:

  • A reference isn't supposed to be null. The default state of a non-nullable reference variable isnot-null. The compiler enforces rules that ensure it's safe to dereference these variables without first checking that it isn't null:
    • The variable must be initialized to a non-null value.
    • The variable can never be assigned the valuenull. The compiler issues a warning when code assigns amaybe-null expression to a variable that shouldn't be null.
  • A reference might be null. The default state of a nullable reference variable ismaybe-null. The compiler enforces rules to ensure that you correctly check for anull reference:
    • The variable can only be dereferenced when the compiler can guarantee that the value isn'tnull.
    • These variables can be initialized with the defaultnull value and can be assigned the valuenull in other code.
    • The compiler doesn't issue warnings when code assigns amaybe-null expression to a variable that might be null.

Any non-nullable reference variable has the initialnull-state ofnot-null. Any nullable reference variable has the initialnull-state ofmaybe-null.

Anullable reference type is noted using the same syntax asnullable value types: a? is appended to the type of the variable. For example, the following variable declaration represents a nullable string variable,name:

string? name;

When nullable reference types are enabled, any variable where the? isn't appended to the type name is anon-nullable reference type. That includes all reference type variables in existing code once you enable this feature. However, any implicitly typed local variables (declared usingvar) arenullable reference types. As the preceding sections showed, static analysis determines thenull-state of local variables to determine if they'remaybe-null before dereferencing it.

Sometimes you must override a warning when you know a variable isn't null, but the compiler determines itsnull-state ismaybe-null. You use thenull-forgiving operator! following a variable name to force thenull-state to benot-null. For example, if you know thename variable isn'tnull but the compiler issues a warning, you can write the following code to override the compiler's analysis:

name!.Length;

Nullable reference types and nullable value types provide a similar semantic concept: A variable can represent a value or object, or that variable might benull. However, nullable reference types and nullable value types are implemented differently: nullable value types are implemented usingSystem.Nullable<T>, and nullable reference types are implemented by attributes read by the compiler. For example,string? andstring are both represented by the same type:System.String. However,int? andint are represented bySystem.Nullable<System.Int32> andSystem.Int32, respectively.

Nullable reference types are a compile time feature. That means it's possible for callers to ignore warnings, intentionally usenull as an argument to a method expecting a non nullable reference. Library authors should include run-time checks against null argument values. TheArgumentNullException.ThrowIfNull is the preferred option for checking a parameter against null at run time. Furthermore, the runtime behavior of a program making use of nullable annotations is the same if all the nullable annotations, (? and!), are removed. Their only purpose is expressing design intent and providing information for null state analysis.

Important

Enabling nullable annotations can change how Entity Framework Core determines if a data member is required. You can learn more details in the article onEntity Framework Core Fundamentals: Working with Nullable Reference Types.

Generics

Generics require detailed rules to handleT? for any type parameterT. The rules are necessarily detailed because of history and the different implementation for a nullable value type and a nullable reference type.Nullable value types are implemented using theSystem.Nullable<T> struct.Nullable reference types are implemented as type annotations that provide semantic rules to the compiler.

  • If the type argument forT is a reference type,T? references the corresponding nullable reference type. For example, ifT is astring, thenT? is astring?.
  • If the type argument forT is a value type,T? references the same value type,T. For example, ifT is anint, theT? is also anint.
  • If the type argument forT is a nullable reference type,T? references that same nullable reference type. For example, ifT is astring?, thenT? is also astring?.
  • If the type argument forT is a nullable value type,T? references that same nullable value type. For example, ifT is aint?, thenT? is also aint?.

For return values,T? is equivalent to[MaybeNull]T; for argument values,T? is equivalent to[AllowNull]T. For more information, see the article onAttributes for null-state analysis in the language reference.

You can specify different behavior usingconstraints:

  • Theclass constraint means thatT must be a non-nullable reference type (for examplestring). The compiler produces a warning if you use a nullable reference type, such asstring? forT.
  • Theclass? constraint means thatT must be a reference type, either non-nullable (string) or a nullable reference type (for examplestring?). When the type parameter is a nullable reference type, such asstring?, an expression ofT? references that same nullable reference type, such asstring?.
  • Thenotnull constraint means thatT must be a non-nullable reference type, or a non-nullable value type. If you use a nullable reference type or a nullable value type for the type parameter, the compiler produces a warning. Furthermore, whenT is a value type, the return value is that value type, not the corresponding nullable value type.

These constraints help provide more information to the compiler on howT is used. That helps when developers choose the type forT and provides betternull-state analysis when an instance of the generic type is used.

Nullable context

Thenullable context determines how nullable reference type annotations are handled and what warnings are produced by static null state analysis. The nullable context contains two flags: theannotation setting and thewarning setting.

Both theannotation andwarning settings are disabled by default for existing projects. Starting in .NET 6 (C# 10), both flags are enabled by default fornew projects. The reason for two distinct flags for the nullable context is to make it easier to migrate large projects that predate the introduction of nullable reference types.

For small projects, you can enable nullable reference types, fix warnings, and continue. However, for larger projects and multi-project solutions, that might generate a large number of warnings. You can use pragmas to enable nullable reference types file-by-file as you begin using nullable reference types. The new features that protect against throwing aSystem.NullReferenceException can be disruptive when turned on in an existing codebase:

  • All explicitly typed reference variables are interpreted as non-nullable reference types.
  • The meaning of theclass constraint in generics changed to mean a non-nullable reference type.
  • New warnings are generated because of these new rules.

Thenullable annotation context determines the compiler's behavior. There are four combinations for thenullable context settings:

  • both disabled: The code isnullable-oblivious.Disable matches the behavior before nullable reference types were enabled, except the new syntax produces warnings instead of errors.
    • Nullable warnings are disabled.
    • All reference type variables are nullable reference types.
    • Use of the? suffix to declare a nullable reference type produces a warning.
    • You can use the null forgiving operator,!, but it has no effect.
  • both enabled: The compiler enables all null reference analysis and all language features.
    • All new nullable warnings are enabled.
    • You can use the? suffix to declare a nullable reference type.
    • Reference type variables without the? suffix are non-nullable reference types.
    • The null forgiving operator suppresses warnings for a possible dereference ofnull.
  • warning enabled: The compiler performs all null analysis and emits warnings when code might dereferencenull.
    • All new nullable warnings are enabled.
    • Use of the? suffix to declare a nullable reference type produces a warning.
    • All reference type variables are allowed to be null. However, members have thenull-state ofnot-null at the opening brace of all methods unless declared with the? suffix.
    • You can use the null forgiving operator,!.
  • annotations enabled: The compiler doesn't emit warnings when code might dereferencenull, or when you assign a maybe-null expression to a non-nullable variable.
    • All new nullable warnings are disabled.
    • You can use the? suffix to declare a nullable reference type.
    • Reference type variables without the? suffix are non-nullable reference types.
    • You can use the null forgiving operator,!, but it has no effect.

The nullable annotation context and nullable warning context can be set for a project using the<Nullable> element in your.csproj file. This element configures how the compiler interprets the nullability of types and what warnings are emitted. The following table shows the allowable values and summarizes the contexts they specify.

ContextDereference warningsAssignment warningsReference types? suffix! operator
disableDisabledDisabledAll are nullableProduces a warningHas no effect
enableEnabledEnabledNon-nullable unless declared with?Declares nullable typeSuppresses warnings for possiblenull assignment
warningsEnabledNot applicableAll are nullable, but members are considerednot-null at opening brace of methodsProduces a warningSuppresses warnings for possiblenull assignment
annotationsDisabledDisabledNon-nullable unless declared with?Declares nullable typeHas no effect

Reference type variables in code compiled in adisabled context arenullable-oblivious. You can assign anull literal or amaybe-null variable to a variable that isnullable-oblivious. However, the default state of anullable-oblivious variable isnot-null.

You can choose which setting is best for your project:

  • Choosedisable for legacy projects that you don't want to update based on diagnostics or new features.
  • Choosewarnings to determine where your code might throwSystem.NullReferenceExceptions. You can address those warnings before modifying code to enable non-nullable reference types.
  • Chooseannotations to express your design intent before enabling warnings.
  • Chooseenable for new projects and active projects where you want to protect against null reference exceptions.

Example:

<Nullable>enable</Nullable>

You can also use directives to set these same flags anywhere in your source code. These directives are most useful when you're migrating a large codebase.

  • #nullable enable: Sets the annotation and warning flags toenable.
  • #nullable disable: Sets the annotation and warning flags todisable.
  • #nullable restore: Restores the annotation flag and warning flag to the project settings.
  • #nullable disable warnings: Set the warning flag todisable.
  • #nullable enable warnings: Set the warning flag toenable.
  • #nullable restore warnings: Restores the warning flag to the project settings.
  • #nullable disable annotations: Set the annotation flag todisable.
  • #nullable enable annotations: Set the annotation flag toenable.
  • #nullable restore annotations: Restores the annotation flag to the project settings.

For any line of code, you can set any of the following combinations:

Warning flagAnnotation flagUse
project defaultproject defaultDefault
enabledisableFix analysis warnings
enableproject defaultFix analysis warnings
project defaultenableAdd type annotations
enableenableCode already migrated
disableenableAnnotate code before fixing warnings
disabledisableAdding legacy code to migrated project
project defaultdisableRarely
disableproject defaultRarely

Those nine combinations provide you with fine-grained control over the diagnostics the compiler emits for your code. You can enable more features in any area you're updating, without seeing more warnings you aren't ready to address yet.

Important

The global nullable context does not apply for generated code files. Under either strategy, the nullable context isdisabled for any source file marked as generated. This means any APIs in generated files are not annotated. No nullable warnings are produced for generated files. There are four ways a file is marked as generated:

  1. In the .editorconfig, specifygenerated_code = true in a section that applies to that file.
  2. Put<auto-generated> or<auto-generated/> in a comment at the top of the file. It can be on any line in that comment, but the comment block must be the first element in the file.
  3. Start the file name withTemporaryGeneratedFile_
  4. End the file name with.designer.cs,.generated.cs,.g.cs, or.g.i.cs.

Generators can opt-in using the#nullable preprocessor directive.

By default, nullable annotation and warning flags aredisabled. That means that your existing code compiles without changes and without generating any new warnings. Beginning with .NET 6, new projects include the<Nullable>enable</Nullable> element in all project templates, setting these flags toenabled.

These options provide two distinct strategies toupdate an existing codebase to use nullable reference types.

Known pitfalls

Arrays and structs that contain reference types are known pitfalls in nullable references and the static analysis that determines null safety. In both situations, a non-nullable reference might be initialized tonull, without generating warnings.

Structs

A struct that contains non-nullable reference types allows assigningdefault for it without any warnings. Consider the following example:

using System;#nullable enablepublic struct Student{    public string FirstName;    public string? MiddleName;    public string LastName;}public static class Program{    public static void PrintStudent(Student student)    {        Console.WriteLine($"First name: {student.FirstName.ToUpper()}");        Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");        Console.WriteLine($"Last name: {student.LastName.ToUpper()}");    }    public static void Main() => PrintStudent(default);}

In the preceding example, there's no warning inPrintStudent(default) while the non-nullable reference typesFirstName andLastName are null.

Another more common case is when you deal with generic structs. Consider the following example:

#nullable enablepublic struct S<T>{    public T Prop { get; set; }}public static class Program{    public static void Main()    {        string s = default(S<string>).Prop;    }}

In the preceding example, the propertyProp isnull at run time. It's assigned to non-nullable string without any warnings.

Arrays

Arrays are also a known pitfall in nullable reference types. Consider the following example that doesn't produce any warnings:

using System;#nullable enablepublic static class Program{    public static void Main()    {        string[] values = new string[10];        string s = values[0];        Console.WriteLine(s.ToUpper());    }}

In the preceding example, the declaration of the array shows it holds non-nullable strings, while its elements are all initialized tonull. Then, the variables is assigned anull value (the first element of the array). Finally, the variables is dereferenced causing a runtime exception.

Constructors

Constructor of a class will still call the finalizer, even when there was an exception thrown by that constructor.
The following example demonstrates that behavior:

public class A{    private string _name;    private B _b;    public A(string name)    {        ArgumentNullException.ThrowIfNullOrEmpty(name);        _name = name;        _b = new B();    }  ~A()  {      Dispose();  }  public void Dispose()  {      _b.Dispose();      GC.SuppressFinalize(this);  }}public class B: IDisposable{    public void Dispose() { }}public void Main(){    var a = new A(string.Empty);}

In the preceding example, theSystem.NullReferenceException will be thrown when_b.Dispose(); runs, if thename parameter wasnull. The call to_b.Dispose(); won't ever throw when the constructor completes successfully. However, there's no warning issued by the compiler, because static analysis can't determine if a method (like a constructor) completes without a runtime exception being thrown.

See also

Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, seeour contributor guide.

Feedback

Was this page helpful?

YesNo

In this article

Was this page helpful?

YesNo