Null and references
Forms that compile (20)
Section titled “Forms that compile (20)”| Syntax | Description | Note |
|---|---|---|
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 == null | Comparing with null | |
s is null | Matching against null | |
other != null | Comparing interface values | |
s ?? "x" | Another value if null | |
s ??= "x" | Assign if null | |
target?.name | Don’t call if null | |
target?.Rotate(...) | Don’t call if null (no return value) | |
#nullable enable | Enabling the check | |
string? s | Types that can be null | |
#nullable disable | Disabling the check | |
s! | Treating as not null | |
if (s != null) { s.Length } | After a null check | |
where T : notnull | Constraining to types that cannot be null | |
[NotNull] | Marking a value as not null | |
[NotNullWhen(true)] out string s | Marking not-null when the result is true | |
[return: MaybeNull] | Marking a return value as possibly null | |
[AllowNull] | Marking a target as accepting null |
Forms that don’t compile (4)
Section titled “Forms that don’t compile (4)”| Syntax | Description | Error | Reason | Alternative |
|---|---|---|---|---|
c as Transform | Become null if the conversion fails | TUKI0001 | not yet | If 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 null | TUKI0001 | runtime | Wrap it in if (a != null) |
IShape s = new Box(); s == null | Comparing an interface implemented on a value type with null | TUKI0001 | by design | Value types can’t be null. You can compare it if the interface is on a Behaviour. |
Point p = new Point(); p == null | Comparing a struct with null | CS0019 | by design | Value types can’t be null |
| When the annotations and attributes take effect | At 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 halt | Calling 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 caught | No. With no exception mechanism, you can’t catch it and carry on |
Where null comes from
Section titled “Where null comes from”Reference-type fields and array elements are null until a value is assigned. Without an initializer, they hold null from the start.
Assigning to a reference type
Section titled “Assigning to a reference type”using UnityEngine;using Tsukimi;
public class CvNullLiteral : TsukimiBehaviour{ void Start() { string s = null; Debug.Log(s == null); // => true }}Array elements right after creation
Section titled “Array elements right after creation”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" }}Inner arrays right after creation
Section titled “Inner arrays right after creation”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 }}Checking for null
Section titled “Checking for null”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.
Comparing with null
Section titled “Comparing with null”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 }}Matching against null
Section titled “Matching against null”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 }}Comparing interface values
Section titled “Comparing interface values”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(); } }}Ways to avoid null
Section titled “Ways to avoid null”A form that writes the behaviour for null inside an expression, without wrapping it in if.
Another value if null
Section titled “Another value if null”using UnityEngine;using Tsukimi;
public class ExpressionsNullCoalesce : TsukimiBehaviour{ void Start() { string s = null; string r = s ?? "x"; Debug.Log(r); // => "x" }}Assign if null
Section titled “Assign if null”using UnityEngine;using Tsukimi;
public class ExpressionsNullCoalesceAssign : TsukimiBehaviour{ void Start() { string s = null; s ??= "x"; Debug.Log(s); // => "x" }}Don’t call if null
Section titled “Don’t call if null”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 }}Don’t call if null (no return value)
Section titled “Don’t call if null (no return value)”using UnityEngine;using Tsukimi;
public class ExpressionsNullConditionalVoid : TsukimiBehaviour{ public Transform target;
void Start() { target?.Rotate(Vector3.up); }}Null safety
Section titled “Null safety”| What it does | It 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 errors | Using 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 applies | Off by default. Only from the line where you write #nullable enable onward |
| When it takes effect | At compile time only. No check survives into the finished program, so it can’t prevent halting on null at runtime |
Enabling the check
Section titled “Enabling the check”#nullable enableusing UnityEngine;using Tsukimi;
public class PreNullableEnable : TsukimiBehaviour{ void Start() { string s = "a"; Debug.Log(s.Length); // => 1 }}#nullable enableusing UnityEngine;using Tsukimi;
public class R_nrt_assign : TsukimiBehaviour{ void Start() { string s = null; Debug.Log(s); }}Types that can be null
Section titled “Types that can be null”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 enableusing UnityEngine;using Tsukimi;
public class R_nrt_deref : TsukimiBehaviour{ void Start() { string? s = null; Debug.Log(s.Length); }}Disabling the check
Section titled “Disabling the check”#nullable enableusing UnityEngine;using Tsukimi;
public class NrDisable : TsukimiBehaviour{ void Start() { #nullable disable string s = null; Debug.Log(s == null); // => true }}#nullable enableusing UnityEngine;using Tsukimi;
public class R_nrt_no_disable : TsukimiBehaviour{ void Start() { string s = null; Debug.Log(s == null); }}Treating as not null
Section titled “Treating as not null”Adding ! lets you pass a possibly-null value straight into a type without ?.
#nullable enableusing 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 enableusing 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"; }}After a null check
Section titled “After a null check”#nullable enableusing 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 enableusing UnityEngine;using Tsukimi;
public class R_nrt_no_check : TsukimiBehaviour{ void Start() { string? s = Get(); Debug.Log(s.Length); }
private string? Get() { return "ab"; }}Constraining to types that cannot be null
Section titled “Constraining to types that cannot be null”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 enableusing 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; }}Marking a value as not null
Section titled “Marking a value as not null”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";}Marking not-null when the result is true
Section titled “Marking not-null when the result is true”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; }}Marking a return value as possibly null
Section titled “Marking a return value as possibly null”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; }}Marking a target as accepting null
Section titled “Marking a target as accepting 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";}