コンテンツにスキップ

null と参照

書き方説明
string s = null;参照型への代入
new string[4]生成した直後の配列の要素
new int[4][]生成した直後の内側の配列
buffer == nullnull との比較
s is nullnull との照合
other != nullインタフェースの値との比較
s ?? "x"null なら別の値
s ??= "x"null なら代入
target?.namenull なら呼ばない
target?.Rotate(...)null なら呼ばない(戻り値なし)
#nullable enable検査の有効化
string? snull になりうる型
#nullable disable検査の無効化
s!null でない扱い
if (s != null) { s.Length }確かめたあとの扱い
where T : notnullnull にならない型への限定
書き方説明エラー理由代わりに
c as Transform変換できなければ null にするTUKI0001未実装型パターン(c is Transform t)で受ける
a?[0]配列が null なら要素を読まないTUKI0001環境if (a != null) で囲む
IShape s = new Box(); s == null値型に付けたインタフェースを null と比べるTUKI0001設計値型は null にならない。Behaviour に付けたものなら比べられる
Point p = new Point(); p == nullstruct を null と比べるCS0019設計値型は null にならない

null の参照のメンバを呼んだり、null の配列の要素を読んだりすると、そのイベントはそこで止まります。エラーは実行環境が出します。 例外の仕組みが無いので、捕まえて続きを走らせることはできません。

参照型のフィールドと配列の要素は、値を入れるまで null です。初期化子を書かなければ、最初から null が入っています。

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
}
}

null かどうかは、参照型の値に対してだけ調べられます。値型は 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
}
}

== null と同じ判定を、パターンとして書く形です(パターン)。

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

null と比べられるのは、Behaviour に付けたインタフェースだけです。値型に付けたインタフェースは、値型と同じく 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();
}
}
}

null のときのふるまいを、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"
}
}

左が null のときは、その先の呼び出しをせずに、式全体が null になります。編集画面から差し込んでいない公開フィールドは null なので、この例の s は null です。

using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalReference : TsukimiBehaviour
{
public Transform target;
void Start()
{
string s = target?.name;
Debug.Log(s); // => null
}
}

null なら呼ばない(戻り値なし)

Section titled “null なら呼ばない(戻り値なし)”
using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalVoid : TsukimiBehaviour
{
public Transform target;
void Start()
{
target?.Rotate(Vector3.up);
}
}

null 安全は、参照型を「null になりうる型(string?)」と「ならない型(string)」に分けて、 コンパイラが値の流れを追う仕組みです。null になりうる値を確かめずに使うと、コンパイラは TUKI0201 を出してそこで止まります。C# のコンパイラは同じ形を警告として報告しますが、 この言語ではエラーになり、コンパイルが通りません。

検査は既定では効きません。#nullable enable と書いた行から先だけで効きます。

効くのはコンパイルの時だけです。できあがるプログラムに検査は残らないので、実行時に null で 止まることは防げません。

#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);
}
}

型に ? を付けると、null が入りうる値として扱われます。付けなければ、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);
}
}

! を付けると、null になりうる値を ? の無い型へそのまま渡せます。検査が通るだけで値は変わらないので、実際に null の値のメンバを呼べば、実行時にそこで止まります。

#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; }
}