From 2f9cbbc48c94d02e06eacb82d41726d8c6c0b53d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 20 Nov 2024 16:08:06 -0500 Subject: [PATCH] Add examples for nullability and null states (#1192) * first pass at null-state examples. * build warning * Fix test build issues * Respond to initial feedback * warnings, not errors * one more error tester fix * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Neal Gafter --------- Co-authored-by: Neal Gafter --- standard/types.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index 08e554d00..5396c28b0 100644 --- a/standard/types.md +++ b/standard/types.md @@ -863,8 +863,103 @@ The ***default null state*** of an expression is determined by its type, and the A diagnostic can be produced when a variable ([§9.2.1](variables.md#921-general)) of a non-nullable reference type is initialized or assigned to an expression that is maybe null when that variable is declared in text where the annotation flag is enabled. +> *Example*: Consider the following method where a parameter is nullable and that value is assigned to a non-nullable type: +> +> +> ```csharp +> #nullable enable +> public class C +> { +> public void M(string? p) +> { +> // Assignment of maybe null value to non-nullable variable +> string s = p; +> } +> } +> ``` +> +> The compiler may issue a warning where the parameter that might be null is assigned to a variable that should not be null. If the parameter is null-checked before assignment, the compiler may use that in its nullable state analysis and not issue a warning: +> +> +> ```csharp +> #nullable enable +> public class C +> { +> public void M(string? p) +> { +> if (p != null) +> { +> string s = p; +> // Use s +> } +> } +> } +> ``` +> +> *end example* + The compiler can update the null state of a variable as part of its analysis. -> *Note*: The compiler can treat a property ([§15.7](classes.md#157-properties)) as either a variable with state, or as independent get and set accessors ([§15.7.3](classes.md#1573-accessors)). In other words, a compiler can choose if writing to a property changes the null state of reading the property. +> *Example*: The compiler may choose to update the state based on any statements in your program: +> +> +> ```csharp +> #nullable enable +> public void M(string? p) +> { +> // p is maybe-null +> int length = p.Length; +> +> // p is not null. +> string s = p; +> +> if (s != null) +> { +> int l2 = s.Length; +> } +> // s is maybe null +> int l3 = s.Length; +> } +> ``` +> +> In the previous example, the compiler may decide that after the statement `int length = p.Length;`, the null-state of `p` is not-null. If it were null, that statement would have thrown a `NullReferenceException`. This is similar to the behavior if the code had been preceded by `if (p == null) throw NullReferenceException();` except that the code as written may produce a warning, the purpose of which is to warn that an exception may be thrown implicitly. + +Later in the method, the code checks that `s` is not a null reference. The null-state of `s` can change to maybe null after the null-checked block closes. The compiler can infer that `s` is maybe null because the code was written to assume that it might have been null. Generally, when the code contains a null check, the compiler may infer that the value might have been null.*end example* + + + +> *Example*: The compiler can treat a property ([§15.7](classes.md#157-properties)) as either a variable with state, or as independent get and set accessors ([§15.7.3](classes.md#1573-accessors)). In other words, a compiler can choose whether writing to a property changes the null state of reading the property, or if reading a property changes the null state of that property. +> +> +> ```csharp +> class Test +> { +> private string? _field; +> public string? DisappearingProperty +> { +> get +> { +> string tmp = _field; +> _field = null; +> return tmp; +> } +> set +> { +> _field = value; +> } +> } +> +> static void Main() +> { +> var t = new Test(); +> if (t.DisappearingProperty != null) +> { +> int len = t.DisappearingProperty.Length; +> } +> } +> } +> ``` +> +> In the previous example, the backing field for the `DisappearingProperty` is set to null when it is read. However, a compiler may assume that reading a property doesn't change the null state of that expression. *end example* ***End of conditionally normative text***