Unity 2020.1から、自動実装プロパティのバッキングフィールドにSerializeField属性をつけた時のEditor上の表示が良くなったが、シリアライズ名的に使ってはいけない


前提

  • Unityのフィールドに付与するSerializeField属性がわかる
  • C#のプロパティ、自動実装プロパティ、バッキングフィールドがわかる
  • C# 7.3から、自動実装プロパティに属性をつけられるようになったことを知っている

以上の内容は、自分の『C# 7.3から自動実装プロパティのバッキングフィールドに属性をつけられるようになった。UnityでSerializeFieldとそれをいい感じに使いたかった』で説明してます。

本題

次のようなコードを、自動実装プロパティのバッキングフィールドにSerializeField属性をつけて、シリアライズを試みます。

using UnityEngine;

public class Player : MonoBehaviour
{
    [field: SerializeField]
    public int Level { get; private set; }
}

Unity 2019.4までで、自動実装プロパティのバッキングフィールドにSerializeField属性をつけた時のEditor上の表示はこんな感じ。

そして.unityファイル中(YAMLファイル)などでのシリアライズはこんな感じ。

<Level>k__BackingField」!

これは自動実装プロパティのバッキングフィールドの名前です。ILとしては有効でも、C#のフィールド名としては不正な名前です。

この名前でEditor上で表示され、そしてシリアライズされます。

ここまではUnity 2019.4までの話。


ここからはUnity 2020.1以降の話。

Unity 2020.1以降では、先のコンポーネントのUnity Editor上の表示はこんな感じになります。

なんと表示が変わっています。「Level」という表示になりました。断定はできませんが内部実装でUnityEditor.ObjectNames.NicifyVariableNameを使っている可能性があります。

// Levelと表示された
Debug.Log(ObjectNames.NicifyVariableName("<Level>k__BackingField"));

さぁ、ではUnity 2020.1では、どんな名前でシリアライズされるのでしょうか?

残念ながら以前と変わらず、ILとしては有効でも、C#のフィールド名としては不正な「<Level>k__BackingField」っていう名前でシリアライズされるようです。


自動実装プロパティのバッキングフィールドに対してSerializeFieldをつけた時の、Unity 2019.4 => Unity 2020.1の仕様変更は・・・

  • Unity Editor上の表示は変更(プロパティ名になり、簡潔になった。)
  • .unityや.prefabなどでシリアライズされている名前は変わらず

となります。


さて、タイトルが自分の主張なのですが、「Unity 2020.1から、自動実装プロパティのバッキングフィールドに、SerializeField属性をつけた時のEditor上の表示は良くなったが、シリアライズ名的に使ってはいけない」です。

ぱっと見のUnity Editor上の表示は良い感じに見えます。しかし、シリアライズは「<Level>k__BackingField」という名称で行われます。そのため、自動実装プロパティのバッキングフィールドに、SerializeField属性を単純につけることは、避けることを強くおすすめします。

自分は「シリアライズされる名前」・「シリアライズされたり、永続化される際のデータフォーマット」というのは非常に重要と考えています。コードを変更するコストに比べて、シリアライズ・永続化されたデータのフォーマットを変更するというのは、影響範囲が広く、過去・未来のことを考慮する必要があり、非常に面倒だからです。

<Level>k__BackingFieldという自動実装プロパティのバッキングフィールドの名前は

  • 冗長
  • 扱いにくい
  • C#のフィールド名として不正
  • C#の自動実装プロパティのバッキングフィールドの命名が仕様化されていない

という欠点があります。そのため、自分は「シリアライズされる名前」・「シリアライズされたり、永続化される際のデータフォーマット」として不適切だと考えています。

多少冗長になりますが、こんな感じで書くことをおすすめします。

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private int level;
    public int Level => level;
}

こんなことができればよかったのに

2017年から主張しているけれど、「Unityでシリアライズ名を指定する機能が欲しい」。

SerializeFieldが引数をとって、シリアライズ名を指定できればよかったのに・・・

public class Player : MonoBehaviour
{
    // こんなことができればよかったのに 
    [field: SerializeField("field")]
    public int Level { get; private set; }
}

FormerlySerializedAsAttributeってのがあるけれど、残念ながらこれはダメ。「以前シリアライズされていた名前を指定するもの」だから。


ちなみにもし、「自動実装プロパティのバッキングフィールドにSerializeFieldをつけた時の名前が、勝手に良い感じになれば良いのにー」って思った人がいたら、それはNot Goodだと思います。「SerializeFieldをつけた時のシリアライズされる名前はフィールド名で」っていう原則が崩れちゃうので。(自動実装プロパティのバッキングフィールドもフィールドだから)

やっぱり必要なのは明示的にシリアライズする名前を指定する方法。(Unity 2020.1やUnity 2020.2の時点でそんな方法はない、ですよね?あったら教えてください。)