Null and references
Forms that compile (16)
Section titled “Forms that compile (16)”| Syntax | Description |
|---|---|
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 } | Handling after a check |
where T : notnull | Restricting to types that cannot be null |
Forms that don’t compile (4)
Section titled “Forms that don’t compile (4)”| Syntax | Description | Error | Reason | Instead |
|---|---|---|---|---|
c as Transform | Become null if the conversion fails | TUKI0001 | not yet | Receive it with a type pattern (c is Transform t) |
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 |
Calling a member on a null reference, or reading an element of a null array, stops the event there. The runtime raises the error. There is no exception mechanism, so you can’t catch it and keep running.
Where null comes from
Section titled “Where null comes from”Reference type fields and array elements are null until you assign a value. Without an initializer, they start out null.
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”Only interfaces implemented on a Behaviour can be compared with null. Interfaces implemented on a value type can’t, because the value type itself can’t be null.
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 after it doesn’t run, and the whole expression becomes null. A public field not wired up in the inspector is null, so s is null in this example.
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”Null safety divides reference types into a type that can be null (string?) and a type that cannot (string),
and the compiler tracks the flow of values. If you use a possibly-null value without checking it, the compiler
reports TUKI0201 and stops there. The C# compiler reports the same form as a warning,
but in this language it’s an error, and the code doesn’t compile.
The check is off by default. It only applies from the line where you write #nullable enable.
The check applies only at compile time. It doesn’t remain in the built program, so it doesn’t prevent execution from stopping 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 treats it as a value that can be null. Without it, the value is treated as one that can’t be 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 directly to a type without ?. Only the check is satisfied, not the value, so calling a member on an actually-null value still stops there at runtime.
#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"; }}Handling after a check
Section titled “Handling after a 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"; }}Restricting to types that cannot be null
Section titled “Restricting 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; }}