Skip to content

Commit

Permalink
fix links
Browse files Browse the repository at this point in the history
  • Loading branch information
BillWagner committed Dec 11, 2024
1 parent 58123df commit be915bc
Showing 1 changed file with 257 additions and 1 deletion.
258 changes: 257 additions & 1 deletion standard/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,263 @@ An *unmanaged_type* is any type that isn’t a *reference_type*, a *type_paramet
- `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`.
- Any *enum_type*.
- Any user-defined *struct_type* that is not a constructed type and contains instance fields of *unmanaged_type*s only.
- In unsafe code ([§22.2](unsafe-code.md#222-unsafe-contexts)), any *pointer_type* ([§22.3](unsafe-code.md#223-pointer-types)).
- In unsafe code ([§23.2](unsafe-code.md#232-unsafe-contexts)), any *pointer_type* ([§23.3](unsafe-code.md#233-pointer-types)).

## 8.9 Reference Types and nullability

### 8.9.1 General

A *nullable reference type* is denoted by appending a *nullable_type_annotation* (`?`) to a non-nullable reference type. There is no semantic difference between a non-nullable reference type and its corresponding nullable type, both can either be a reference to an object or `null`. The presence or absence of the *nullable_type_annotation* declares whether an expression is intended to permit null values or not. A compiler may provide diagnostics when an expression is not used according to that intent. The null state of an expression is defined in [§8.9.5](types.md#895-nullabilities-and-null-states). An identity conversion exists among a nullable reference type and its corresponding non-nullable reference type ([§10.2.2](conversions.md#1022-identity-conversion)).

There are two forms of nullability for reference types:

- *nullable*: A *nullable-reference-type* can be assigned `null`. Its default null state is *maybe-null*.
- *non-nullable*: A *non-nullable reference* should not be assigned a `null` value. Its default null state is *not-null*.

> *Note:* The types `R` and `R?` are represented by the same underlying type, `R`. A variable of that underlying type can either contain a reference to an object or be the value `null`, which indicates “no reference.” *end note*
The syntactic distinction between a *nullable reference type* and its corresponding *non-nullable reference type* enables a compiler to generate diagnostics. A compiler must allow the *nullable_type_annotation* as defined in [§8.2.1](types.md#821-general). The diagnostics must be limited to warnings. Neither the presence or absence of nullable annotations, nor the state of the nullable context can change the compile time or runtime behavior of a program except for changes in any diagnostic messages generated at compile time.

### 8.9.2 Non-nullable reference types

A ***non-nullable reference type*** is a reference type of the form `T`, where `T` is the name of the type. The default null-state of a non-nullable variable is *not-null*. Warnings may be generated when an expression that is *maybe-null* is used where a *not-null* value is required.

### 8.9.3 Nullable reference types

A reference type of the form `T?` (such as `string?`) is a ***nullable reference type***. The default null-state of a nullable variable is *maybe null*. The annotation `?` indicates the intent that variables of this type are nullable. The compiler can recognize these intents to issue warnings. When the nullable annotation context is disabled, using this annotation can generate a warning.

### 8.9.4 Nullable context

#### 8.9.4.1 General

Every line of source code has a ***nullable context***. The annotations and warnings flags for the nullable context control nullable annotations ([§8.9.4.3](types.md#8943-nullable-annotations)) and nullable warnings ([§8.9.4.4](types.md#8944-nullable-warnings)), respectively. Each flag can be *enabled* or *disabled*. The compiler can use static flow analysis to determine the null state of any reference variable. A reference variable’s null state ([§8.9.5](types.md#895-nullabilities-and-null-states)) is either *not null*, *maybe null*, or *maybe default*.

The nullable context may be specified within source code via nullable directives ([§6.5.9](lexical-structure.md#659-nullable-directive)) and/or via some implementation-specific mechanism external to the source code. If both approaches are used, nullable directives supersede the settings made via an external mechanism.

The default state of the nullable context is implementation defined.

Throughout this specification, all C# code that does not contain nullable directives, or about which no statement is made regarding the current nullable context state, shall be assumed to have been compiled using a nullable context where both annotations and warnings are enabled.

> *Note:* A nullable context where both flags are disabled matches the previous standard behavior for reference types. *end note*
#### 8.9.4.2 Nullable disable

When both the warning and annotations flags are disabled, the nullable context is *disabled*.

When the nullable context is ***disabled***:

- No warning shall be generated when a variable of an unannotated reference type is initialized with, or assigned a value of, `null`.
- No warning shall be generated when a variable of a reference type that possibly has the null value.
- For any reference type `T`, the annotation `?` in `T?` generates a message and the type `T?` is the same as `T`.
- For any type parameter constraint `where T : C?`, the annotation `?` in `C?` generates a message and the type `C?` is the same as `C`.
- For any type parameter constraint `where T : U?`, the annotation `?` in `U?` generates a message and the type `U?` is the same as `U`.
- The generic constraint `class?` generates a warning message. The type parameter must be a reference type.
> *Note*: This message is characterized as “informational” rather than “warning,” so as not to confuse it with the state of the nullable warning setting, which is unrelated. *end note*
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) has no effect.

> *Example*:
>
> <!-- Example: {template:"code-in-main-without-using", name:"NullableAnnotationContext1", ignoredWarnings:["CS0219","CS8632"], expectedException:"NullReferenceException"} -->
> ```csharp
> #nullable disable annotations
> string? s1 = null; // Informational message; ? is ignored
> string s2 = null; // OK; null initialization of a reference
> s2 = null; // OK; null assignment to a reference
> char c1 = s2[1]; // OK; no warning on dereference of a possible null;
> // throws NullReferenceException
> c1 = s2![1]; // OK; ! is ignored
> ```
>
> *end example*
#### 8.9.4.3 Nullable annotations
When the warning flag is disabled and the annotations flag is enabled, the nullable context is *annotations*.
When the nullable context is ***annotations***:
- For any reference type `T`, the annotation `?` in `T?` indicates that `T?` a nullable type, whereas the unannotated `T` is non-nullable.
- No diagnostic warnings related to nullability are generated.
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) may alter the analyzed null state of its operand and what compile time diagnostic warnings are produced.
> *Example*:
>
> <!-- Example: {template:"code-in-main-without-using", name:"NullableAnnotationContext2", ignoredWarnings:["CS0219"], expectedException:"NullReferenceException"} -->
> ```csharp
> #nullable disable warnings
> #nullable enable annotations
> string? s1 = null; // OK; ? makes s2 nullable
> string s2 = null; // OK; warnings are disabled
> s2 = null; // OK; warnings are disabled
> char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException
> c1 = s2![1]; // No warnings
> ```
>
> *end example*
#### 8.9.4.4 Nullable warnings
When the warning flag is enabled and the annotations flag is disabled, the nullable context is *warnings*.
When the nullable context is ***warnings***, a compiler can generate diagnostics in the following cases:
- A reference variable that has been determined to be *maybe null*, is dereferenced.
- A reference variable of a non-nullable type is assigned to an expression that is *maybe null*.
- The `?` is used to note a nullable reference type.
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) is used to set the null state of its operand to *not null*.
> *Example*:
>
> <!-- Example: {template:"code-in-main-without-using", name:"NullableAnnotationContext3", ignoredWarnings:["CS0219"], expectedWarnings:["CS8632", "CS8602"], expectedException:"NullReferenceException"} -->
> ```csharp
> #nullable disable annotations
> #nullable enable warnings
> string? s1 = null; // OK; ? makes s2 nullable
> string s2 = null; // OK; null-state of s2 is "maybe null"
> s2 = null; // OK; null-state of s2 is "maybe null"
> char c1 = s2[1]; // Warning; dereference of a possible null;
> // throws NullReferenceException
> c1 = s2![1]; // The warning is suppressed
> ```
>
> *end example*
#### 8.9.4.5 Nullable enable
When both the warning flag and the annotations flag are enabled, the nullable context is *enabled*.
When the nullable context is ***enabled***:
- For any reference type `T`, the annotation `?` in `T?` makes `T?` a nullable type, whereas the unannotated `T` is non-nullable.
- The compiler can use static flow analysis to determine the null state of any reference variable. When nullable warnings are enabled, a reference variables null state ([§8.9.5](types.md#895-nullabilities-and-null-states)) is either *not null*, *maybe null*, or *maybe default* and
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) sets the null state of its operand to *not null*.
- The compiler can issue a warning if the nullability of a type parameter doesnt match the nullability of its corresponding type argument.
### 8.9.5 Nullabilities and null states
A compiler is not required to perform any static analysis nor is it required to generate any diagnostic warnings related to nullability.
**The remainder of this subclause is conditionally normative.**
A compiler that generates diagnostic warnings conforms to these rules.
Every expression has one of three ***null state***s:
- *maybe null*: The value of the expression may evaluate to null.
- *maybe default*: The value of the expression may evaluate to the default value for that type.
- *not null*: The value of the expression isn’t null.
The ***default null state*** of an expression is determined by its type, and the state of the annotations flag when it is declared:
- The default null state of a nullable reference type is:
- Maybe null when its declaration is in text where the annotations flag is enabled.
- Not null when its declaration is in text where the annotations flag is disabled.
- The default null state of a non-nullable reference type is not null.
> *Note:* The *maybe default* state is used with unconstrained type parameters when the type is a non-nullable type, such as `string` and the expression `default(T)` is the null value. Because null is not in the domain for the non-nullable type, the state is maybe default. *end note*
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:
>
> <!-- Example: {template:"code-in-class-lib", name:"NullableAssignment", expectedWarnings:["CS8600"]} -->
> ```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:
>
> <!-- Example: {template:"code-in-class-lib", name:"NullChecked"} -->
> ```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.
> *Example*: The compiler may choose to update the state based on any statements in your program:
>
> <!-- Example: {template:"code-in-class-lib", name:"UpdateStates", expectedWarnings:["CS8602","CS8602"]} -->
> ```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*
<!-- markdownlint-disable MD028 -->
<!-- markdownlint-enable MD028 -->
> *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.
>
> <!-- Example: {template:"standalone-console", name:"NullPropertyAnalysis"} -->
> ```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***

Check failure on line 981 in standard/types.md

View workflow job for this annotation

GitHub Actions / lint

Multiple consecutive blank lines [Expected: 1; Actual: 2]
## 8.9 Reference Types and nullability

Check failure on line 982 in standard/types.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content [Context: "8.9 Reference Types and nullab..."]
Expand Down

0 comments on commit be915bc

Please sign in to comment.