The nullable reference types feature allows developers to express which APIs can accept and return null values. Some framework APIs, such as generic APIs, or TryXxx methods use specific patterns that the normal type system of C# can't express. This proposal lists all the attributes.
```C#
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property)]
public sealed class AllowNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property)]
public sealed class DisallowNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue)]
public sealed class MaybeNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue)]
public sealed class NotNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class MaybeNullWhenAttribute : Attribute
{
public MaybeNullWhenAttribute(bool returnValue);
public bool ReturnValue { get; }
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class NotNullWhenAttribute : Attribute
{
public NotNullWhenAttribute(bool returnValue);
public bool ReturnValue { get; }
}
[AttributeUsage(AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullIfNotNullAttribute : Attribute
{
public NotNullIfNotNullAttribute(string parameterName);
public string ParameterName { get; }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)]
public sealed class DoesNotReturnAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class DoesNotReturnIfAttribute : Attribute
{
public DoesNotReturnIfAttribute(bool parameterValue);
public bool ParameterValue { get; }
}
}
```
Should the "Maybe" values be "MayBe"? As in "this may be null", vs "this maybe null" (which has no verb)
+1, "this maybe null" is not grammatical as far as I know
I am having hard time guessing what some of the attributes do, e.g. AllowNullAttribute. Is there a spec with more details? Alternative, add a example of an existing framework method for each attribute.
DoesNotReturnAttribute
This is generally useful. It is not tied to nullable annotations. Should it be regular attribute, not related to NullableAnnotationAttribute?
they can be grouped
Where do we expect the grouping to be used? Null somewhere in the name is a good enough clue for humans that the attribute has something to do with nullable annotations.
@jcouv's LDM notes at https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-05-15.md include more in-depth descriptions from @MadsTorgersen as well as examples I compiled from around Corelib.
This is generally useful. It is not tied to nullable annotations. Should it be regular attribute, not related to NullableAnnotationAttribute?
Yes, [DoesNotReturn] is not directly tied to nullability. It impacts reachability in general.
For reference, I have a local draft of a change that employs these in corelib. Appx usage counts end up being something like this:
Could you include attributes target in the proposal? It would clarify the intention where they can be used.
Here's what I have in my local branch. Obviously this can be tweaked, it's just what I put in place to be able to make progress deploying the attributes across Corelib:
```C#
namespace System.Runtime.CompilerServices
{
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class AllowNullAttribute : Attribute { }
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class DisallowNullAttribute : Attribute { }
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class MaybeNullAttribute : Attribute { }
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullAttribute : Attribute { }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
public NotNullIfNotNullAttribute(string parameterName)
{
ParameterName = parameterName ?? throw new ArgumentNullException(nameof(parameterName));
}
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class DoesNotReturnAttribute : Attribute { }
/// <summary>Specifies that the method will not return if the associated Boolean property is passed the specified value.</summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be unreachable if the argument to the associated parameter
/// matches this value.
/// </param>
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
}
```
A full draft change for Corelib is here:
https://github.com/dotnet/coreclr/pull/24679
Summary:
NullableAttribute (emitted by the compiler for question marks) will still go to compiler services.System.Diagnostics.CodeAnalysis namespaceHere is the API shape we agreed on:
```C#
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property)]
public sealed class AllowNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property)]
public sealed class DisallowNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue)]
public sealed class MaybeNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue)]
public sealed class NotNullAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class MaybeNullWhenAttribute : Attribute
{
public MaybeNullWhenAttribute(bool returnValue);
public bool ReturnValue { get; }
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class NotNullWhenAttribute : Attribute
{
public NotNullWhenAttribute(bool returnValue);
public bool ReturnValue { get; }
}
[AttributeUsage(AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.ReturnValue, AllowMultiple = true)]
public sealed class NotNullIfNotNullAttribute : Attribute
{
public NotNullIfNotNullAttribute(string parameterName);
public string ParameterName { get; }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)]
public sealed class DoesNotReturnAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class DoesNotReturnIfAttribute : Attribute
{
public DoesNotReturnIfAttribute(bool parameterValue);
public bool ParameterValue { get; }
}
}
```
@bartonjs @terrajobst was maybe vs may be (ie Maybe vs MayBe) discussed in the review? maybe it's just me but it sounds wrong as Maybe
Looping @cston. A prompt resolution on naming will be appreciated to avoid blocking his work to recognize the attributes in the compiler. Thanks
maybe it's just me but it sounds wrong as Maybe
We did discuss it at the review. It's not just you. It's the difference between "the state of this may be null" and "the state of this is 'maybe null'". I read it as the former, in which case MayBeNull is better. Others read it as the latter, in which case MaybeNull is better. I still prefer the former, but there were arguments made about the latter being a) consistent with "not null" (e.g. "the state of this is 'not null'" vs "the state of this not null", the latter of which obviously doesn't make sense), and b) being easier to type/read. No one had a strong enough preference on the former case to override the stronger opinions on the latter case.
OK fine. Sounds good. For what it's worth, I read "if you can exchange it with 'perhaps' then maybe is potentially correct. if you can exchange it will 'could be' then may be is potentially correct". Which is basically the difference you describe.
This has been checked in.
I'm struggling to work out whether any of these attributes has the meaning that I quite often want of "this method doesn't return (because it'll throw an exception) if the parameter was null".
For example, I'd want to be able to write:
string? foo = // Get value from somewhere.
Preconditions.CheckNotNull(foo, nameof(foo)); // Ideally using CallerExpressionAttribute, of course...
Console.WriteLine(foo.Length);
It feels like I want a mixture of NotNullWhen and DoesNotReturn, but I can't see how to do that.
Have I missed something, or should I file a feature request?
(I can make Preconditions.CheckNotNull accept a T? and return a T which sorts out a lot of uses, but not all.)
@jskeet The LDM notes say:
Open question: would we also need a
[DoesNotReturnWhenNull(nameof(parameter))]?
I think that's what you're looking for, but it does not exist (yet?).
Others said they want to see this attribute too, see https://github.com/dotnet/roslyn/issues/35816#issuecomment-504151108 and https://github.com/dotnet/csharplang/issues/2536#issuecomment-493608421.
@svick: Fantastic, thanks for being more on the ball than I am.
Filed https://github.com/dotnet/roslyn/issues/37544 for tracking (post C# 8).
@jskeet @svick You can do void M([NotNull] string? x) today and get the effect that [DoesNotReturnWhenNull(nameof(x))] would presumably have.
@jcouv: Maybe it's just the brief description of the method that confuses me, but I don't see how NotNullAttribute would do that. This is about the input of the method rather than the output of the method. Is there any more concrete documentation on this that we could read?
I was wrong. [NotNull] does not work on by-value/in parameters.
The best documentation we have on nullable annotation attributes is https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-05-15.md
The doc says that [NotNull] only applies to outputs (returns values, out/ref parameters) and the compiler did implement that.
Verified with the following test:
```C#
[Fact]
public void Test()
{
var source = @"
using System;
public class C
{
void M2(string? x)
{
M(x);
x.ToString(); // warning
}
void M([NotNull] string? x) // when this method returns, x was not null
{
if (x is null) throw null!;
}
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
public sealed class NotNullAttribute : Attribute
{
}";
var comp = CreateNullableCompilation(source);
comp.VerifyDiagnostics(
// (10,9): warning CS8602: Dereference of a possibly null reference.
// x.ToString(); // warning
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 9)
);
}
```
I'm investigating further...
I see code that should learn from post-conditions on inputs, which matches my recollection. But the behavior shown by test above shows no effect.
switch (refKind)
{
case RefKind.None:
case RefKind.In:
{
// learn from post-conditions [Maybe/NotNull, Maybe/NotNullWhen] without using an assignment
learnFromPostConditions(argument, parameterAnnotations);
}
break;
Will continue discussion in https://github.com/dotnet/roslyn/issues/37544
Most helpful comment
Should the "Maybe" values be "MayBe"? As in "this may be null", vs "this maybe null" (which has no verb)