The basic idea of this extension is to change the behavior of ReSharper's static nullability analysis so that specific code elements get a default nullability annotation without specifying an explicit [NotNull]
or [CanBeNull]
attribute. For example, reference types in method parameters are by default [NotNull]
(→ they need an explicit [CanBeNull]
to become nullable).
With enabled Implicit Nullability for specific, configurable, syntax elements, the following rules apply.
- Reference types are by default implicitly
[NotNull]
. - Their nullability can be overridden with an explicit
[CanBeNull]
attribute. - Optional method parameters with a
null
default value are implicitly[CanBeNull]
.
In a nutshell, the code showed in the picture above ...
public string Bar(string a, [CanBeNull] string b, string c = null)
{
// ...
}
... implicitly becomes ...
[NotNull]
public string Bar([NotNull] string a, [CanBeNull] string b, [CanBeNull] string c = null)
{
// ...
}
Without this extension the default nullability value is "unknown" which means that ReSharper excludes these elements for its nullability analysis. As a result of the changed default nullability of this extension we have to place [CanBeNull]
annotations only for specific code elements (e.g. a reference type parameter where it should be allowed to pass null
as argument) and don't need explicit annotations for the majority of cases (in code bases which try to reduce passing null
references to a minimum).
ReSharper 9.2 introduced support for nullability analysis for async
(Task<T>
return typed) methods. With enabled Implicit Nullability for method return values, Task<T>
return typed methods also become implicitly [ItemNotNull]
(ReSharper uses this attribute to refer to the value of Task<T>
). For nullable Task<T>
return values, this can be overridden with [ItemCanBeNull]
.
The following example program contains a potential NullReferenceException
in command.Equals("Hello")
because the programmer missed that GetCommand()
could also return null
.
public static void Main(string[] args)
{
string command = GetCommand(args);
if (command.Equals("Hello"))
Console.WriteLine("Hello World!");
}
private static string GetCommand(string[] args)
{
if (args.Length < 1)
return null;
return args[0];
}
With enabled Implicit Nullability this bug would have been detected by ReSharper's static analysis.
- ReSharper would warn about returning
null
inGetCommand()
because this method would be implicitly annotated as[NotNull]
. - This warning would be solved by the programmer by adding
[CanBeNull]
toGetCommand()
. - As a consequence of the
[CanBeNull]
attribute, ReSharper would now warn about the potentialNullReferenceException
in thecommand.Equals("Hello")
call inMain()
.
In the example above Implicit Nullability forces the programmer to fix the missing [CanBeNull]
attribute on GetCommand()
. This shows how the number of [CanBeNull]
annotations will be increased in the code base and therefore doesn't only improve ReSharper's static analysis but also the documentation of method signatures (contracts).
Another goal of this extension is to bring ReSharper's static analysis in sync with the implicit null checks of Fody NullGuard. For example, this Fody weaver injects throw new ArgumentNullException(/*...*/)
statements for method parameters into method bodies using the same rules as Implicit Nullability. In other words this weaver adds runtime checks for nullability to ReSharper's static analysis.
C# 8 will get Nullable reference types which brings option types into the C# language. This matches the semantics of Implicit Nullability and goes even further because it extends it to locals, generic parameters, ... because the nullability annotation works on type level.
[CanBeNull]
string Bar([CanBeNull] string a, string b)
{
string result = a;
return result;
}
... then corresponds to ...
string? Bar(string? a, string b)
{
string? result = a;
return result;
}
This will make Implicit Nullability unnecessary in the future and at the same time a perfect "upgrade path" because a good [CanBeNull]
-annotated code base will be easily portable to C# 8.
Since version 2016.1 ReSharper supports (in the "internal mode") a comparable feature (see Code Inspection | Settings) with the following differences.
Implicit Nullability ...
- ... rules can be enabled / disabled for specific code elements like parameters or (readonly) fields.
- ... supports (project specific) configuration by code.
- ... adds additional warnings.
- ... adds type highlighting for explicit or implicit
[NotNull]
elements.
Explicit or implicit [NotNull]
element types are highlighted with a dotted underline. (See the pink underlines in the Bar
-method in the sample screenshot above.) This helps to recognize all [NotNull]
elements, especially inferred [NotNull]
elements from a base class and code elements which are configured as implicitly [NotNull]
.
The highlighting can be enabled/disabled on the Implicit Nullability options page, and the colors can be configured in Visual Studio's "Fonts and Colors" options.
Implicit nullability can be enabled or disabled for specific syntax elements in the Code Inspection | Implicit Nullability options page.
Implicit Nullability can also be configured by code using an AssemblyMetadataAttribute
. This has the advantage that the configuration gets compiled into the assembly so that consumers of the assembly with installed Implicit Nullability get the same implicit nullability annotations of the compiled code elements as within the library's solution.
Example:
[assembly: AssemblyMetadata("ImplicitNullability.AppliesTo",
"InputParameters, RefParameters, OutParametersAndResult, Fields, Properties")]
[assembly: AssemblyMetadata("ImplicitNullability.Fields", "RestrictToReadonly")]
[assembly: AssemblyMetadata("ImplicitNullability.Properties", "RestrictToGetterOnly")]
[assembly: AssemblyMetadata("ImplicitNullability.GeneratedCode", "Exclude")]
In addition to the behavior change of the nullability analysis the following code inspection warnings are provided by this extension.
For more information about these warnings (and their motivation) see their description in the ReSharper Code Inspection | Inspection Severity options page.
Id: ImplicitNotNullConflictInHierarchy
abstract class Base
{
public abstract void Method([CanBeNull] string a);
}
class Derived : Base
{
// "Implicit NotNull conflicts with nullability in base type":
public override void Method(string a)
{
}
}
Id: ImplicitNotNullElementCannotOverrideCanBeNull
abstract class Base
{
[CanBeNull]
public abstract string Method();
}
class Derived : Base
{
// "Implicit NotNull element cannot override CanBeNull in base type, nullability should be explicit":
public override string Method() => "";
}
"Implicit NotNull overrides unknown nullability of base member, nullability should be explicit" resp. hint "Implicit NotNull result or out parameter overrides unknown nullability of base member, nullability should be explicit"
Ids: ImplicitNotNullOverridesUnknownBaseMemberNullability
resp. ImplicitNotNullResultOverridesUnknownBaseMemberNullability
class Derived : External.Class /* (external code with unannotated 'Method' and 'Function') */
{
// "Implicit NotNull overrides unknown nullability of base member, nullability should be explicit":
public override void Method(string a)
{
}
// "Implicit NotNull result or out parameter overrides unknown nullability of base member,
// nullability should be explicit":
public override string Function() => "";
}
Id: NotNullOnImplicitCanBeNull
// "Implicit CanBeNull element has an explicit NotNull annotation":
void Foo([NotNull] int? a)
{
}
Big thanks to Fabian Schmied for supporting the design and conception of Implicit Nullability.