Skip to content

Add support for nullable references #700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1907897
add support for nullable references
RexJaeschke Jan 9, 2023
46ee8b4
add support for nullable references
RexJaeschke Jan 9, 2023
9099d63
Update lexical-structure.md
RexJaeschke Jan 9, 2023
df2ef4f
add support for nullable references
RexJaeschke Jan 9, 2023
95dc9c9
Update types.md
RexJaeschke Jan 9, 2023
acfa49f
add support for nullable references
RexJaeschke Jan 9, 2023
092edcc
Update conversions.md
RexJaeschke Jan 9, 2023
6dec0bb
add support for nullable references
RexJaeschke Jan 9, 2023
df6a682
add support for nullable references
RexJaeschke Jan 9, 2023
1c8069c
add support for nullable references
RexJaeschke Jan 10, 2023
0f4ee57
add support for nullable references
RexJaeschke Jan 10, 2023
e57dcc6
add support for nullable references
RexJaeschke Jan 10, 2023
90f2304
add support for nullable references
RexJaeschke Jan 10, 2023
e063116
add support for nullable references
RexJaeschke Jan 10, 2023
290f362
add support for nullable references
RexJaeschke Jan 10, 2023
42e8c2f
add support for nullable references
RexJaeschke Jan 10, 2023
74379b6
add support for nullable references
RexJaeschke Jan 10, 2023
9611e9d
Update expressions.md
RexJaeschke Jan 16, 2023
b74ea84
add annotation to new examples
RexJaeschke Jan 25, 2023
a98212f
add annotation to new examples
RexJaeschke Jan 25, 2023
7f68369
add annotation to new examples
RexJaeschke Jan 25, 2023
ca1c87e
fix md formatting
RexJaeschke Feb 6, 2023
6f249a4
fix md formatting
RexJaeschke Feb 6, 2023
034390a
fix md formatting
RexJaeschke Feb 6, 2023
7dd37fe
fix md formatting
RexJaeschke Feb 6, 2023
7c00ec3
add more support for nullable references
RexJaeschke Feb 7, 2023
859d5fb
add more support for nullable references
RexJaeschke Feb 7, 2023
7c90336
Update statements.md
RexJaeschke Jul 24, 2023
4af6674
Update classes.md
RexJaeschke Jul 24, 2023
09284ef
fix merged section links
BillWagner Sep 25, 2023
d4c95ac
fixup headers
BillWagner Sep 25, 2023
ba95362
final build fixes
BillWagner Sep 25, 2023
dfeb75e
quote grammar literal
RexJaeschke Oct 28, 2023
4b0f862
identify code-analysis attribute namespace
RexJaeschke Oct 30, 2023
a113d0d
Update Library.cs
RexJaeschke Oct 30, 2023
d68135b
Update Program.cs
RexJaeschke Oct 30, 2023
708b98f
Update Library.cs
RexJaeschke Oct 30, 2023
1dd01ec
Update Program.cs
RexJaeschke Oct 30, 2023
c127987
Update Library.cs
RexJaeschke Oct 30, 2023
20178b7
Update standard/expressions.md
Nigel-Ecma Mar 10, 2024
46bf2fe
Update standard/expressions.md
Nigel-Ecma Mar 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion standard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
- [§10.5.3](conversions.md#1053-evaluation-of-user-defined-conversions) Evaluation of user-defined conversions
- [§10.5.4](conversions.md#1054-user-defined-implicit-conversions) User-defined implicit conversions
- [§10.5.5](conversions.md#1055-user-defined-explicit-conversions) User-defined explicit conversions
- [§10.6](conversions.md#106-conversions-involving-nullable-types) Conversions involving nullable types
- [§10.6](conversions.md#106-conversions-involving-nullable-value-types) Conversions involving nullable value types
- [§10.6.1](conversions.md#1061-nullable-conversions) Nullable Conversions
- [§10.6.2](conversions.md#1062-lifted-conversions) Lifted conversions
- [§10.7](conversions.md#107-anonymous-function-conversions) Anonymous function conversions
Expand Down
2 changes: 1 addition & 1 deletion standard/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Because of array covariance, assignments to elements of reference type arrays in
> ```csharp
> class Test
> {
> static void Fill(object[] array, int index, int count, object value)
> static void Fill(object?[] array, int index, int count, object value)
> {
> for (int i = index; i < index + count; i++)
> {
Expand Down
277 changes: 274 additions & 3 deletions standard/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,13 +518,14 @@ The attribute instance represented by `T`, `C`, `P`, and `N`, and associated wi

### 22.5.1 General

A small number of attributes affect the language in some way. These attributes include:
A number of attributes affect the language in some way. These attributes include:

- `System.AttributeUsageAttribute` ([§22.5.2](attributes.md#2252-the-attributeusage-attribute)), which is used to describe the ways in which an attribute class can be used.
- `System.Diagnostics.ConditionalAttribute` ([§22.5.3](attributes.md#2253-the-conditional-attribute)), is a multi-use attribute class which is used to define conditional methods and conditional attribute classes. This attribute indicates a condition by testing a conditional compilation symbol.
- `System.ObsoleteAttribute` ([§22.5.4](attributes.md#2254-the-obsolete-attribute)), which is used to mark a member as obsolete.
- `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§22.5.5](attributes.md#2255-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method.
- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§22.5.6.2](attributes.md#22562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§22.5.6.3](attributes.md#22563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§22.5.6.4](attributes.md#22564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters.
- The code analysis attributes (§Code-Analysis-Attributes).

An execution environment may provide additional implementation-specific attributes that affect the execution of a C# program.

Expand Down Expand Up @@ -787,8 +788,8 @@ When an optional parameter is annotated with one of the caller-info attributes,
> ```csharp
> public void Log(
> [CallerLineNumber] int line = -1,
> [CallerFilePath] string path = null,
> [CallerMemberName] string name = null
> [CallerFilePath] string? path = null,
> [CallerMemberName] string? name = null
> )
> {
> Console.WriteLine((line < 0) ? "No line" : "Line "+ line);
Expand Down Expand Up @@ -857,6 +858,276 @@ For invocations that occur within field or event initializers, the member name u

For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent.

### §Code-Analysis-Attributes Code analysis-attributes

#### §Code-Analysis-Attributes-General General

Code compiled with both nullable contexts (§Nullable-Contexts) disabled is null oblivious (§Nullabilities-And-Null-States). That means any reference type variable may be null, but null checks aren't required. Once such code is made nullable-aware, those rules change. Reference type variables should never have the null value, and such variables must be checked against null before being dereferenced.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all sounds quite explanatory - it's useful text, but perhaps should be in a note? Personally I'm all for the spec being a little less formal in places - this may be something to talk about in the meeting, as I suspect we'll get more and more features specified in this way.


Some APIs have more complex rules for when variables can or can't be null. In these cases, one or more of the nullable-related attributes described below can be used to express those rules. When user code is compiled in a nullable-enabled context, the compiler is required to warn when that code violates those rules. That is, these attributes help define the nullability contract for an API.

The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`.

**Attribute** | **Meaning**
------------------ | ------------------
`AllowNull` (§The-AllowNull-Attribute) | A non-nullable argument may be null.
`DisallowNull` (§The-DisallowNull-Attribute) | A nullable argument should never be null.
`MaybeNull` (§The-MaybeNull-Attribute) | A non-nullable return value may be null.
`NotNull` (§The-NotNull-Attribute) | A nullable return value will never be null.
`MaybeNullWhen` (§The-MaybeNullWhen-Attribute) | A non-nullable argument may be null when the method returns the specified `bool` value.
`NotNullWhen` (§The-NotNullWhen-Attribute) | A nullable argument won't be null when the method returns the specified `bool` value.
`NotNullIfNotNull` (§The-NotNullIfNotNull-Attribute) | A return value isn't null if the argument for the specified parameter isn't null.
`MemberNotNull` (§The-MemberNotNull-Attribute) | The listed member won't be null when the method returns.
`MemberNotNullWhen` (§The-MemberNotNullWhen-Attribute) | The listed member won't be null when the method returns the specified `bool` value.
`DoesNotReturn` (§The-DoesNotReturn-Attribute) | This method never returns.
`DoesNotReturnIf` (§The-DoesNotReturnIf-Attribute) | This method never returns if the associated `bool` parameter has the specified value.

#### §The-AllowNull-Attribute The AllowNull attribute

Specifies that a null value is allowed as an input even if the corresponding type disallows it.

> *Example*: Consider the following read/write property that never returns `null` because it has a reasonable default value. However, a user can give null to the set accessor to set the property to that default value.
>
> <!-- Example: {template:"standalone-lib", name:"AllowNullAttribute", replaceEllipsis:true, customEllipsisReplacements:["\"XYZ\""]} -->
> ```csharp
> #nullable enable
> public class X
> {
> [AllowNull]
> public string ScreenName
> {
> get => _screenName;
> set => _screenName = value ?? GenerateRandomScreenName();
> }
> private string _screenName = GenerateRandomScreenName();
> private static string GenerateRandomScreenName() => ...;
> }
> ```
>
> Given the following use of that property’s set accessor
>
> ```csharp
> var v = new X();
> v.ScreenName = null; // without attribute AllowNull, get a warning
> ```
>
> without the attribute, the compiler is required to generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. *end example*

#### §The-DisallowNull-Attribute The DisallowNull attribute

Specifies that a null value is disallowed as an input even if the corresponding type allows it.

> *Example*: Consider the following property in which null is the default value, but clients can only set it to a non-null value.
>
> <!-- Example: {template:"standalone-lib", name:"DisallowNullAttribute"} -->
> ```csharp
> #nullable enable
> public class X
> {
> [DisallowNull]
> public string ReviewComment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be declared as public string? ReviewComment?

> {
> get => _comment;
> set => _comment = value ?? throw new ArgumentNullException(nameof(value),
> "Cannot set to null");
> }
> private string _comment = "";
> }
> ```
>
> The get accessor could return the default value of `null`, so the compiler warns that it must be checked before access. Furthermore, it warns callers that, even though it could be null, callers shouldn't explicitly set it to null. *end example*

#### §The-DoesNotReturn-Attribute The DoesNotReturn attribute

Specifies that a given method never returns.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we might want this to be in a different feature PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I haven't checked whether there's a corresponding change to reachability, but I can imagine all kinds of ways in which this could affect the standard even if we didn't have the NRT feature.)


> *Example*: Consider the following:
>
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnAttribute"} -->
> ```csharp
> public class X
> {
> [DoesNotReturn]
> private void FailFast()
> {
> throw new InvalidOperationException();
> }
>
> public void SetState(object containedField)
> {
> if (!isInitialized)
> {
> FailFast();
> }
> // unreachable code:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not actually unreachable code, is it? (We only call isInitialized conditionally.)

> _field = containedField;
> }
>
> private bool isInitialized = false;
> private object _field;
> }
> ```
>
> The presence of the attribute helps the compiler in a number of ways. First, the compiler issues a warning if there's a path where the method can exit without throwing an exception. Second, the compiler marks any code after a call to that method as unreachable, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. *end example*

#### §The-DoesNotReturnIf-Attribute The DoesNotReturnIf attribute

Specifies that a given method never returns if the associated `bool` parameter has the specified value.

> *Example*: Consider the following:
>
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnIfAttribute"} -->
> ```csharp
> public class X
> {
> private void FailFastIf([DoesNotReturnIf(false)] bool isValid)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside, I think this method could do with being renamed. I'm used to "if" conditions happening if something is true, not false.

> {
> if (!isValid)
> {
> throw new InvalidOperationException();
> }
> }
>
> public void SetFieldState(object containedField)
> {
> FailFastIf(isInitialized);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an example of how the method name is weird, this reads as "fail fast if initialized" whereas we want to fail if the state is not initialized.

> // unreachable code when "isInitialized" is false:
> _field = containedField;
> }
>
> private bool isInitialized = false;
> private object _field;
> }
> ```
>
> *end example*

#### §The-MaybeNull-Attribute The MaybeNull attribute

Specifies that a non-nullable return value may be null.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly null, or make the end of the example just null. (Basically they should be consistent.)


> *Example*: Consider the following generic method:
>
> <!-- Example: {template:"code-in-class-lib-without-using", name:"MaybeNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```csharp
> #nullable enable
> public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
> ```
>
> If `T` is replaced by `string`, `T?` becomes a nullable annotation. If `T` is replaced by `int`, `T?` becomes an `int?`. When `Find` searches an `IEnumerable<string>`, the default return is `null`, but when `Find` searches an `IEnumerable<int>`, the default return is 0. As such, specifying the return type as `T?` isn’t appropriate. However, adding this attribute solves the problem:
>
> <!-- Example: {template:"code-in-class-lib", name:"MaybeNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```csharp
> #nullable enable
> [return: MaybeNull]
> public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
> ```
>
> The attribute informs callers that the contract implies a non-nullable type, but the return value may actually be `null`. *end example*

#### §The-MaybeNullWhen-Attribute The MaybeNullWhen attribute

Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value.

#### §The-MemberNotNull-Attribute The MemberNotNull attribute

Specifies that the given member won't be `null` when the method returns.

> *Example*: The compiler analyzes constructors and field initializers to make sure that all non-nullable reference fields have been initialized before each constructor returns. However, the compiler doesn't track field assignments through all helper methods. The compiler issues a warning when fields aren't initialized directly in the constructor, but rather in a helper method. This warning is suppressed by applying the `MemberNotNull` attribute to a method declaration and specifying the fields that are initialized to a non-null value in the method. For example, consider the following example:
>
> <!-- Example: {template:"standalone-lib", name:"MemeberNotNullAttribute"} -->
> ```csharp
> #nullable enable
> public class Container
> {
> private string _uniqueIdentifier; // must be initialized.
> private string? _optionalMessage;
>
> public Container()
> {
> Helper();
> }
>
> public Container(string message)
> {
> Helper();
> _optionalMessage = message;
> }
>
> [MemberNotNull(nameof(_uniqueIdentifier))]
> private void Helper()
> {
> _uniqueIdentifier = DateTime.Now.Ticks.ToString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> _uniqueIdentifier = DateTime.Now.Ticks.ToString();
> _uniqueIdentifier = DateTime.UtcNow.Ticks.ToString();

(It's not a great unique identifier anyway, but I'd generally want to discourage the use of DateTime.Now...)

> }
> }
> ```
>
> Multiple field names may be given as arguments to the attribute’s constructor. *end example*

#### §The-MemberNotNullWhen-Attribute The MemberNotNullWhen attribute

Specifies that the listed member won't be `null` when the method returns the specified `bool` value.

> *Example*: This attribute is like `MemberNotNull` (§The-MemberNotNull-Attribute) except that `MemberNotNullWhen` takes a `bool` argument. `MemberNotNullWhen` is intended for use in situations in which a helper method returns a `bool` indicating whether it initialized fields. *end example*

#### §The-NotNull-Attribute The NotNull attribute

Specifies that a nullable return value will never be `null`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description feels wrong to me - there's no return value involved.


> *Example*: Consider the following:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullAttribute"} -->
> ```csharp
> #nullable enable
> public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") =>
> _ = value ?? throw new ArgumentNullException(valueExpression);
>
> public static void LogMessage(string? message)
> {
> ThrowWhenNull(message, nameof(message));
> Console.WriteLine(message.Length);
> }
> ```
>
> When null reference types are enabled, method `ThrowWhenNull` compiles without warnings. When that method returns, the `value` argument is guaranteed to be not `null`. However, it's acceptable to call `ThrowWhenNull` with a null reference. *end example*

#### §The-NotNullIfNotNull-Attribute The NotNullIfNotNull attribute

Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`.

> *Example*: Sometimes the null state of a return value depends on the null state of one or more arguments. Such a method will return a non-null value whenever certain arguments aren't `null`. To correctly annotate these methods, add the `NotNullIfNotNull` attribute. Consider the following method:
>
> <!-- Example: {template:"code-in-class-lib-without-using", name:"NotNullIfNotNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
> ```csharp
> #nullable enable
> string GetTopLevelDomainFromFullUrl(string url) { ... }
> ```
>
> If the `url` argument isn't `null`, `null` isn’t returned. When nullable references are enabled, that signature works correctly, provided the API never accepts a null argument. However, if the argument could be null, then return value could also be null. To express that contract correctly, annotate this method as follows:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullIfNotNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
> ```csharp
> #nullable enable
> [return: NotNullIfNotNull("url")]
> string? GetTopLevelDomainFromFullUrl(string? url) { ... }
> ```
>
> *end example*

#### §The-NotNullWhen-Attribute The NotNullWhen attribute

Specifies that a nullable argument won't be `null` when the method returns the specified `bool` value.

> *Example*: The library method `String.IsNullOrEmpty(String)` returns `true` when the argument is `null` or an empty string. It's a form of null-check: Callers don't need to null-check the argument if the method returns `false`. To make a method like this nullable aware, make the parameter type a nullable reference type, and add the NotNullWhen attribute:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullWhenAttribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```csharp
> #nullable enable
> bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
> ```
>
> *end example*

## 22.6 Attributes for interoperation

For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name.
Expand Down
12 changes: 7 additions & 5 deletions standard/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ The behavior of the garbage collector can be controlled, to some degree, via sta
> {
> static void Main()
> {
> B b = new B(new A());
> B? b = new B(new A());
> b = null;
> GC.Collect();
> GC.WaitForPendingFinalizers();
Expand Down Expand Up @@ -1069,19 +1069,21 @@ The behavior of the garbage collector can be controlled, to some degree, via sta
>
> class B
> {
> public A Ref;
> public A? Ref;
>
> ~B()
> {
> Console.WriteLine("Finalize instance of B");
> Ref.F();
> if (Ref != null)
> {
> Ref.F();
> }
> }
>
> class Test
> {
> public static A RefA;
> public static B RefB;
> public static A? RefA;
> public static B? RefB;
>
> static void Main()
> {
Expand Down
Loading