3rd term 4th week - dsuz/csharp GitHub Wiki

今回のテーマ

  • デストラクター
  • 属性 (attribute)
  • 条件付きブレークポイント

準備

  1. CSharp3-4.zip をダウンロードして展開し、.sln ファイルを Visual Studio で開く
  2. CSharp3-4.unitypackage をダウンロードし、Unity のプロジェクトにインポートする

デストラクター

コンストラクターは、何もない状態からインスタンスをメモリ上に生成し、変数に格納する処理だ、というような説明を以前にした。型が構造体である場合は値を格納し、クラスである場合は参照を格納する。

一方、デストラクターとは「インスタンスを破棄する時に実行される処理」である。デストラクタは以下のように書ける。

class MyClass
{
	~MyClass()	// ~クラス名 と書くことでデストラクタを定義できる
    {
        // 破棄する時に実行する処理を書く
    }
}

デストラクターはインスタンスが破棄される時に呼び出されるが、通常の C# ではインスタンスの破棄は「そのインスタンスの参照が全てなくなった」に行われる。「後」というのはなくなってすぐ、というわけではなく、なくなった後に「ガベージコレクション」という処理が実行された後に破棄される。従って、参照していない(プログラムからは一切使われていない)状態になってから、メモリが解放されるまでにタイムラグがある、ということになる。

明示的な破棄

メモリをたくさん使う処理(例: ファイルからデータを読み込む)をする時は、処理が終わったらメモリをすぐに解放したい。このような場合は、前述のガベージコレクションの仕組みでは不安がある。そのような場合は、using 句を使うことで「そのスコープを抜けたらすぐにメモリを解放する」ことができる。

ここに書かれているコード例のように、using 句を使った場合は、そこで宣言された変数はブロックを抜けたらすぐにメモリを解放する。

自分で作ったクラスを using 句で使いたい場合は、IDisposable インターフェイスを実装する必要がある。

Unity でのメモリ管理

普通の C# のクラスでは「コンストラクター → 処理 → ガベージコレクションまたは明示的な破棄により破棄する」という大まかな流れをたどるが、Unity でのメモリ管理はこれとは違う仕組みで行われている。

正確に言うと、Unity の「GameObject やコンポーネント」は普通の C#(これも正確に言うと「.NET Framework」と表現すべき)とは異なるメモリ管理が行われている。Vector3 や Color など「GameObject でもコンポーネントでもない」型は普通の C# と同じように「new でコンストラクターを呼び...」という流れになっている。

インスタンスの生成 (Unity)

まず、インスタンス生成過程が Unity では通常と異なる。GameObject そのものは new キーワードつまりコンストラクターを使ってインスタンスを生成できるが、プレハブを生成する時は Instatiate 関数を使う。コンポーネントは「GameObject にコンポーネントが追加されていればプレハブ生成時にインスタンス化される」し、そうでない場合は AddComponent メソッドを実行した時にインスタンスが生成される。コンポーネントは new キーワードを使ってインスタンス化することはできない(※1)。自作コンポーネントにコンストラクターを書くことも避けた方がよい(※2)。

(※1)new キーワードを使ってコンポーネントのクラスをインスタンス化すると以下の警告が出力される。

You are trying to create a MonoBehaviour using the 'new' keyword.  This is not allowed.  MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all

(※2)例えば、コンポーネント クラスのコンストラクターで GameObject の name を取得しようとすると以下のエラーが出力される。

UnityException: get_gameObject is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'ComponentName' on game object 'GameObjectName'.
See "Script Serialization" page in the Unity Manual for further details.

null チェック

Unity では変数の参照が null であるかチェックする時に以下のように書くことができる。

Rigidbody rb = GetComponent<Rigidbody>();

if (rb)
{
	rb.AddForce(Vector3.up * 30f);
}
else
{
	Debug.LogWarning("Rigidbody is null");
}

これは Unity の場合でのみできる。一般的な C# のプログラムではこういうことはできず、if (list == null) のように書かなくてはいけない。このように書けるのは、変数が GameObject またはコンポーネントの場合のみである。これができるのは、GameObjectComponent(MonoBehaviour) は UnityEngine.Object クラスを継承しており、UnityEngine.Object クラスには演算子として bool が定義されており、インスタンスが参照されている場合は true を返し、参照されていない場合は false を返すようになっているからである。

インスタンスの破棄 (Unity)

Unity の場合は GameObject のインスタンスを破棄する時に Destroy メソッドを使う。同じ「破棄」という言葉を使うが、一般的な C# のプログラムでの破棄と Unity での破棄は挙動が異なる。

Unity では前述のリンク先のマニュアルにある通り、Destroy メソッドによって破棄を命令された後の Update 後に破棄される。破棄された後はインスタンス変数は null になり、シーンからも消えるが、C# 的には実は破棄されていない。Unity 的に GameObject が破棄された後に、さらに C# 的に破棄が行われる。つまり一般的な C# と破棄の取り扱いが異なる。

これにより、OnDestroy() のコールバックを実行したり、特に難しく考えることなく破棄を行うことができるが、メモリが解放されることは破棄されて即行われるわけではないことは知っておいた方がよいでしょう。

Unity と C# (.NET Framework/Mono) のオブジェクトライフサイクルの違い

通常のクラス・構造体 IDisposable GameObject コンポーネント
インスタンス化 new new new, Instantiate, または Hierarchy に配置する GameObject.AddComponent() 等により GameObject にコンポーネントを追加する、プレハブに追加しておき Instantiate する
破棄 GC(※1) GC (※1), using スコープを抜けた時(※2), または Dispose() が呼ばれた時(※2) Destroy()(※3) Destroy()(※3)(※4)
インスタンス化する時に処理をさせたい場合 コンストラクターに記述する コンストラクターに記述する - Start(), Awake(), OnEnable() 等に記述する
破棄される時に処理をさせたい場合 デストラクターに記述する デストラクターまたは IDisposable.Dispose() に記述する - OnDestroy(), OnDisable() 等に記述する

(※1)ガベージ コレクション

(※2)この時にメモリは即時解放される

(※3)Destroy した後にはもうアクセスできなくなるが、実際にはメモリ上には存在している。この後、ガベージコレクションによりメモリが解放される

(※4)コンポーネントを Destroy() すると、GameObject から Remove される

属性 (attribute)

属性を使うことにより、対象に追加の情報(メタデータ)を与えることができる。主な対象はクラスとフィールドであるが、C# のプログラミング ガイドの 属性 (C#) のページ内の「属性の対象」というセクションに、属性の対象となり得るものの一覧がある。

Unity により定義されている Attribute は AddComponentMenu のリファレンスから、左ペインの目次を探して Attributes 以下を見れば一覧が見つかる。

UnityのAttribute(属性)についてまとめてメモる。 というページも参考になるが、Unity で定義されている属性のみでなく、標準ライブラリの属性で Unity からよく使うものも含まれている。

Unity でよく使われる標準ライブラリの属性

上の UnityのAttribute(属性)についてまとめてメモる。 で触れられていないが、Unity でよく使われている標準ライブラリの属性には以下のものがある。

  1. Unity の JsonUtility クラスを使ってインスタンスをシリアライズしたい時は、クラス(または構造体)を定義する時にクラスに Serializable (System.Serializable) 属性を指定する。(参考: JSON 形式にシリアライズ
  2. 将来的に廃止される (obsolete) なメソッド等を呼び出そうとすると、以下のような警告が出力されるが、これは System.Obsolete 属性を指定されたメソッドを呼ぼうとしているからである。
warning CS0618: 'Application.LoadLevel(string)' is obsolete: 'Use SceneManager.LoadScene'

条件付きブレークポイント

デバッグする時にブレークポイントをよく使うが、「特定の条件を満たした時にのみブレークする」こともできる。これを「条件付きブレークポイント」を設定する、という。

条件付きブレークポイントを指定するには、普通にブレークポイントを指定した後に、以下のようにブレークポイントを右クリックして「条件」を選ぶことで設定できる。

「条件」を選ぶと、以下のようにブレークする条件を設定するフォームが表示される。以下の例は、「入力された文字列が 6 文字以上である場合にブレークする、という設定である。

条件には、条件式以外にも「n 回ここを通過したらブレークする」などの条件を設定することもできる。

参考資料

  • デストラクター
    • 独習 C# 7.4.5 - デストラクター
  • 属性
    • 独習 C# 11.2 - 属性
  • 条件付きブレークポイント
⚠️ **GitHub.com Fallback** ⚠️