Skip to content

Null and references

SyntaxDescriptionNote
string s = null;Assigning to a reference type
new string[4]Array elements right after creation
new int[4][]Inner arrays right after creation
buffer == nullComparing with null
s is nullMatching against null
other != nullComparing interface values
s ?? "x"Another value if null
s ??= "x"Assign if null
target?.nameDon’t call if null
target?.Rotate(...)Don’t call if null (no return value)
#nullable enableEnabling the check
string? sTypes that can be null
#nullable disableDisabling the check
s!Treating as not null
if (s != null) { s.Length }After a null check
where T : notnullConstraining to types that cannot be null
[NotNull]Marking a value as not null
[NotNullWhen(true)] out string sMarking not-null when the result is true
[return: MaybeNull]Marking a return value as possibly null
[AllowNull]Marking a target as accepting null
SyntaxDescriptionErrorReasonAlternative
c as TransformBecome null if the conversion failsTUKI0001not yetIf the type is known, write a cast ((Transform)c). If it’s a union or an interface, receive it with a type pattern.
a?[0]Don’t read the element if the array is nullTUKI0001runtimeWrap it in if (a != null)
IShape s = new Box(); s == nullComparing an interface implemented on a value type with nullTUKI0001by designValue types can’t be null. You can compare it if the interface is on a Behaviour.
Point p = new Point(); p == nullComparing a struct with nullCS0019by designValue types can’t be null
When the annotations and attributes take effectAt compile time only. Neither ? nor any of the [NotNull] family survives into the finished program. They are misleading precisely because they raise no error
Forms that haltCalling a member on a null reference, or reading an element of a null array. That event halts there, and the runtime reports the error
Can it be caughtNo. With no exception mechanism, you can’t catch it and carry on

Reference-type fields and array elements are null until a value is assigned. Without an initializer, they hold null from the start.

using UnityEngine;
using Tsukimi;
public class CvNullLiteral : TsukimiBehaviour
{
void Start()
{
string s = null;
Debug.Log(s == null); // => true
}
}
using UnityEngine;
using Tsukimi;
public class ArraysElementReferenceType : TsukimiBehaviour
{
private string[] names;
void Start()
{
names = new string[4];
Debug.Log(names[0] == null); // => true
names[0] = "hello";
Debug.Log(names[0]); // => "hello"
}
}
using UnityEngine;
using Tsukimi;
public class ArraysDeclareJagged : TsukimiBehaviour
{
private int[][] grid;
void Start()
{
grid = new int[4][];
Debug.Log(grid[0] == null); // => true
grid[0] = new int[4];
grid[0][0] = 1;
Debug.Log(grid[0][0]); // => 1
}
}

You can only check for null on reference type values. Value types can’t be null, so you can’t write the comparison at all.

using UnityEngine;
using Tsukimi;
public class ArraysCompareToNull : TsukimiBehaviour
{
private int[] buffer;
void Start()
{
Debug.Log(buffer == null); // => true
if (buffer == null)
{
buffer = new int[4];
}
Debug.Log(buffer.Length); // => 4
}
}

A form that writes the same check as == null, as a pattern (patterns).

using UnityEngine;
using Tsukimi;
public class PtConstNull : TsukimiBehaviour
{
void Start()
{
string s = null;
if (s is null) { Debug.Log(1); } // => 1
}
}

The only things that can be compared against null are interfaces attached to a Behaviour.

using UnityEngine;
using Tsukimi;
public interface IGadget
{
void Tick();
}
public class IfaceCompareNull : TsukimiBehaviour, IGadget
{
public IGadget other;
public void Tick() { }
void Start()
{
Debug.Log(other == null); // => true
if (other != null)
{
other.Tick();
}
}
}

A form that writes the behaviour for null inside an expression, without wrapping it in if.

using UnityEngine;
using Tsukimi;
public class ExpressionsNullCoalesce : TsukimiBehaviour
{
void Start()
{
string s = null;
string r = s ?? "x";
Debug.Log(r); // => "x"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNullCoalesceAssign : TsukimiBehaviour
{
void Start()
{
string s = null;
s ??= "x";
Debug.Log(s); // => "x"
}
}

When the left side is null, the call beyond it is skipped and the whole expression becomes null.

using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalReference : TsukimiBehaviour
{
public Transform target;
void Start()
{
string s = target?.name;
Debug.Log(s); // => null
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalVoid : TsukimiBehaviour
{
public Transform target;
void Start()
{
target?.Rotate(Vector3.up);
}
}
What it doesIt separates reference types into those that can be null (string?) and those that can’t (string), and the compiler follows the flow of values
When it errorsUsing a possibly-null value without checking it errors on the spot with TUKI0201. The C# compiler reports the same form as a warning; here it is an error and does not compile
Where it appliesOff by default. Only from the line where you write #nullable enable onward
When it takes effectAt compile time only. No check survives into the finished program, so it can’t prevent halting on null at runtime
#nullable enable
using UnityEngine;
using Tsukimi;
public class PreNullableEnable : TsukimiBehaviour
{
void Start()
{
string s = "a";
Debug.Log(s.Length); // => 1
}
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_assign : TsukimiBehaviour
{
void Start()
{
string s = null;
Debug.Log(s);
}
}

Adding ? to a type marks it as a value that may hold null.

using UnityEngine;
using Tsukimi;
public class NrAnnot : TsukimiBehaviour
{
void Start()
{
#nullable enable
string? s = null;
Debug.Log(s == null); // => true
}
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_deref : TsukimiBehaviour
{
void Start()
{
string? s = null;
Debug.Log(s.Length);
}
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class NrDisable : TsukimiBehaviour
{
void Start()
{
#nullable disable
string s = null;
Debug.Log(s == null); // => true
}
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_no_disable : TsukimiBehaviour
{
void Start()
{
string s = null;
Debug.Log(s == null);
}
}

Adding ! lets you pass a possibly-null value straight into a type without ?.

#nullable enable
using UnityEngine;
using Tsukimi;
public class PeNullForgiving : TsukimiBehaviour
{
void Start()
{
string? s = Get();
string t = s!;
Debug.Log(t); // => "a"
}
private string? Get() { return "a"; }
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_no_bang : TsukimiBehaviour
{
void Start()
{
string? s = Get();
string t = s;
Debug.Log(t);
}
private string? Get() { return "a"; }
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class NullNarrowing : TsukimiBehaviour
{
void Start()
{
string? s = Get();
if (s != null) { Debug.Log(s.Length); } // => 2
}
private string? Get() { return "ab"; }
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_no_check : TsukimiBehaviour
{
void Start()
{
string? s = Get();
Debug.Log(s.Length);
}
private string? Get() { return "ab"; }
}
using UnityEngine;
using Tsukimi;
public class GcNotNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<int>(1)); // => 1
}
private int Take<T>(T v) where T : notnull { return 1; }
}
#nullable enable
using UnityEngine;
using Tsukimi;
public class R_nrt_notnull : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<string?>(null));
}
private int Take<T>(T v) where T : notnull { return 1; }
}

Tells C#‘s analysis that an argument or return value is not null.

using UnityEngine;
using Tsukimi;
public class NrAttr : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[System.Diagnostics.CodeAnalysis.NotNull]
private string S = "a";
}

Says the out value is not null when the result is true.

using UnityEngine;
using Tsukimi;
public class NrNotNullWhen : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private bool Try([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string s) { s = "a"; return true; }
}

Says the return value can be null.

using UnityEngine;
using Tsukimi;
public class NrMaybeNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[return: System.Diagnostics.CodeAnalysis.MaybeNull]
private string Get() { return null; }
}

Lets null be assigned even to a type without ?.

using UnityEngine;
using Tsukimi;
public class NrAllowNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
[System.Diagnostics.CodeAnalysis.AllowNull]
private string S = "a";
}