Runtime Nullables automatically adds null checks to method/property entry and exit points based on the standard nullable annotations and attributes available in C# 8+. It is capable of checking input parameters as well as outputs (i.e. return values and out
/ref
parameters) and supports comprehensive checks on the full range of special method types including asynchronous Task<T>
methods, Task<T>
methods that synchronously return completed tasks (i.e. using Task.FromResult<T>
), IEnumerable<T>
/IEnumerator<T>
iterators as well as asynchronous IAsyncEnumerable<T>
/IAsyncEnumerator<T>
iterators. Custom throw helpers can be defined to fully customize the exceptions thrown to your liking.
We are a small team of engineers and designers dedicated to building beautiful, functional and well-engineered software solutions. We offer very competitive rates as well as fixed-price contracts and welcome inquiries to discuss any custom development / project support needs you may have.
Visit https://github.com/Singulink to see our full list of publicly available libraries and other open-source projects.
The package is available on NuGet - simply install the RuntimeNullables.Fody
package into your project and null checks will be automatically injected when your project builds! It is also a good idea to add the latest Fody
package directly to your project to ensure you are using an up-to-date version to avoid issues.
After adding nullable annotation support to NullGuard on top of its legacy functionality, it was apparent that it become rather unweildy. This made expanding features and optimizing behavior for nullable annotations difficult in addition to inheriting an overly complex attribute model. Runtime Nullables was written from the ground up with only nullable annotations in mind to address these issues. Some notable improvements include:
- More efficient weaving algorithm for faster build times.
- Does not force
.initlocals
on methods (better performance, especially withstackalloc
). - Supports checking
ValueTask<T>
results, synchronously returned completeTask<T>
results,IEnumerable<T>
/IEnumerator<T>
iterator values and asynchronousIAsyncEnumerable<T>
/IAsyncEnumerator<T>
iterator values. - Uses throw helpers instead of throwing directly (better performance / smaller IL code) and lets you define your own custom throw helpers.
- Much simpler attribute model that uses a single
[NullChecks(bool)]
attribute to control null check injection. - Designed specifically for NRTs so it is much easier to add advanced functionality such as full validation of conditional attributes like
[MaybeNullWhen]
(planned for a future release). - Outputs warnings for conflicting annotations that cause null checks to be skipped, i.e. if
[AllowNull, DisallowNull]
is applied to a parameter. - Uses
NullReferenceException
instead ofInvalidOperationException
when an output check fails since the latter is often thrown/caught in normal circumstances which can cause null contract violations to go unnoticed in unit tests or exception handling code. - Numerous bug fixes and reliability improvements.
By default, null checks are added based on the build configuration as follows:
Release
: input parameter values on publicly exposed methods/properties onlyDebug
: input and output (ref
/out
) parameter values and return values on all methods/properties
The behavior can be configured in one of two ways: inside your .csproj
file or using a FodyWeavers.xml
configuration file in the root of your project. There are two configuration settings:
CheckOutputs
: If set totrue
then outputs (i.e. return values, ref/out parameters) will be null checked, otherwise these checks are omitted.CheckNonPublic
: If set totrue
then all methods/properties are checked, otherwise only those that are potentially publicly exposed are checked.
If configuring via the .csproj
file then you have to add a <WeaverConfiguration>
property like so:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<WeaverConfiguration>
<Weavers>
<RuntimeNullables CheckNonPublic="true" CheckOutputs="true" />
</Weavers>
</WeaverConfiguration>
</PropertyGroup>
</Project>
If you would like to configure settings with a FodyWeavers.xml
file in the project then simply build the project after adding the RuntimeNullables.Fody
package and it will automatically generate an xml configuration file for you which you can then edit. Alternatively, you can add the file manually with the following contents:
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<RuntimeNullables CheckNonPublic="true" CheckOutputs="true" />
</Weavers>
If you omit the CheckNonPublic
or CheckOutputs
setting then it will fallback to the default behavior based on the build configuration as explained above.
Injection of null checks can be fine-tuned in your code using the [NullChecks(bool)]
attribute which can be applied at the assembly, class, method/property or return value level. For example, if you apply [NullChecks(false)]
to a class it will disable null checks on every member in that class unless it has [NullChecks(true)]
applied to it. It is important to note that if the CheckOutputs
or CheckNonPublic
setting is false
then that will override any [NullChecks(true)]
attribute applied to an output or non-public member. In other words, if the setting is set to false
then those particular checks are never emitted, regardless of the presence of a [NullChecks(true)]
attribute.
Additionally, custom throw helpers can be defined to customize the exceptions that are thrown. In order to do that, you simply add an internal ThrowHelpers
class into your project in the RuntimeNullables
namespace and match the signature of the throw helper you want to override and it will be used instead of the defaults. The default throw helpers are as follows:
namespace RuntimeNullables
{
internal static class ThrowHelpers
{
internal static void ThrowArgumentNull(string paramName)
{
throw new ArgumentNullException(paramName);
}
internal static void ThrowOutputNull(string message)
{
throw new NullReferenceException(message);
}
internal static Exception GetAsyncResultNullException(string message)
{
return new NullReferenceException(message);
}
}
}
If an output check or async result check fails then the message
parameter contains a message specifying exactly what caused the check to fail, i.e. "Enumerator result nullability contract was broken."
using RuntimeNullables;
// Return value is not null checked since it is nullable,
// but method throws ArgumentNullException if messageService is null
public string? GetMessage(MessageService messageService);
// The 'value' parameter is null checked on method entry as well as when the method exits since it is a ref
public void UpdateMessage(ref string value);
// Now the 'value' parameter is only null checked on method entry due to the [MaybeNull] annotation
public void UpdateMessage([MaybeNull] ref string value);
// Each item returned by the enumeration is null checked
public IEnumerable<string> GetMessages()
{
string? nullValue = null;
yield return "some value";
yield return "some other value";
yield return nullValue!; // NullReferenceException is thrown
}
public Task<T> GetValueAsync<T>()
{
return Task.FromResult<T>(default!); // NullReferenceException if T is a reference type
}
[return: NullChecks(false)]
public Task<T> GetValueAsync<T>()
{
return Task.FromResult<T>(default!); // This is okay now since return value null checks are disabled
}
// Result of the task is null checked
public async Task<string> GetMessageAsync()
{
return await MessageService.GetMessageAsync();
}
// All results are null checked
public async IAsyncEnumerable<string> GetValuesAsync()
{
await Task.Delay(100);
yield return "Some value";
await Task.Delay(100);
yield return null!; // NullReferenceException is thrown
}