【Unity】Serializableなクラスにはデフォルトコンストラクタを忘れずつけようという話

Serializableなクラスにはデフォルトコンストラクタを忘れずつけようという話です。

Unity2020.3.35f1

はじめに

Unity で ScriptableObject にシリアライズを行う際に、Serializableなクラスにはデフォルトコンストラクタを忘れてちょっとハマったのでそのメモです。

直面した事象

まず、以下のように適当な ScriptableObject に Serializable なオブジェクトがシリアライズされている状況を考えます。

[CreateAssetMenu]
public class ExampleSO : ScriptableObject
{
    // Exampleクラスをシリアライズする
    public Example example;
}

[Serializable]
public class Example
{
    [NonSerialized] public List<string> Values = new List<string>();
}

この ScriptableObject のアセットを作成し、以下のように Example.Values にアクセスすると、当然ですが 0 という出力が得られます。

public static class Menu
{
    [MenuItem("ExampleSO/Test")]
    private static void Test()
    {
        var assetPath = AssetDatabase.FindAssets($"t: {nameof(ExampleSO)}")
            .Select(AssetDatabase.GUIDToAssetPath)
            .First();
        var obj = AssetDatabase.LoadAssetAtPath<ExampleSO>(assetPath);

        // 0
        Debug.Log(obj.example.Values.Count);
    }
}

ここで、Example クラスに引数付きのコンストラクタを定義します。
この状態で上記のスクリプトを再び実行すると、Values が null になり例外が発生します。

[Serializable]
public class Example
{
    // デシリアライズ時にNonSerializedな参照型が生成されなくなる
    [NonSerialized] public List<string> Values = new List<string>();

    // 引数ありコンストラクタを適当に定義
    public Example(string value)
    {
        Values.Add(value);
    }
}

デフォルトコンストラクタを定義する

以下のように、デフォルトコンストラクタを追加で定義するとこの現象は回避できます。privateでもOKです。

[Serializable]
public class Example
{
    [NonSerialized] public List<string> Values = new List<string>();

    public Example(string value)
    {
        Values.Add(value);
    }

    // デフォルトコンストラクタを定義するとうまく行く(privateでもOK)
    private Example()
    {
    }
}

Serializable なクラスにはデフォルトコンストラクタを定義しようという教訓でした。