Skip to content

Nullability

kevin-montrose edited this page Apr 10, 2021 · 2 revisions

Nullability

Introduction

Cesil can be configured to perform some checks at runtime to prevent null values from occuring where they are not expected. While this functionality is primarily for supporting C# 8's new nullable reference types, checks can be configured in null oblivious contexts and for nullable value types.

Runtime null checks are intended to prevent the subversion of user nullability annotations. Accordingly, if the .NET runtime would prevent null from appearing then Cesil may decline to emit any additional null checks. For example, if a Setter is backed by an instance method and forbids null rows then Cesil may not explicitly check that a row is non-null as the runtime will raise a NullReferenceException before the Setter's method executes.

Row Nullability

Getters, InstanceProviders, Resets, Setters, and ShouldSerializes have a notion of "row nullability" - whether the produced or taken row is allowed to be null. Each exposes an AllowNullRows() and ForbidNullRows() method.

AllowNullRows() returns an equivalent wrapper which will cause no runtime checks for null rows to be performed. Be aware that AllowNullRows() allows you to subvert nullable reference type annotations. It is an error to allow null rows if the row is a non-nullable value type.

ForbidNullRows() returns an equivalent wrapper which will cause a runtime check for null rows to be performed, if it is possible for a null to be present at runtime. It is legal to forbid null rows even if the row is a nullable value type.

It is illegal to call either AllowNullRows() or ForbidNullRows() on a wrapper that does not take or produce a row.

Value Nullability

Formatters, Getters, Parsers, and Setters have a notion of "value nullability" - whether the produced or take value is allowed to be null. Each exposes an AllowNullValues() and ForbidNullValues() method.

AllowNullValues() returns an equivalent wrapper which will cause no runtime checks for null values to be performed. Be aware that AllowNullValues() allows you to subvert nullable reference type annotations. It is an error to allow null values if the value is a non-nullable value type.

ForbidNullValues() returns an equivalent wrapper which will cause a runtime check for null values to be performed, if it is possible for a null to be present at runtime. It is legal to forbid null values even if the value is a nullable value type.

Chaining

For wrappers that allow chaining with Else(), the most restrictive null check will be used by the newly created wrapper.

Concretely, that means that:

  • Formatters
    • if either forbids null values, then the new Formatter will forbid them
  • InstanceProviders and Parsers
    • if the first wrapper in the chain cannot produce nulls (it is backed by a constructor) then the new wrapper will not inject null checks
    • if any part of the chain can produce nulls, then the new wrapper can produce nulls

Once chained, wrappers are treated as a single unit - so using any of the AllowNullXXX() methods may result in subverting null annotations.