search
LoginSignup
7

More than 1 year has passed since last update.

posted at

updated at

Unityで「シリアライズするフィールド」と「プロパティー」を簡潔に書きたくて、SourceGeneratorを作ってみた

※ この投稿で紹介するSourceGeneratorは、まだUnityじゃ使えません。将来的にこうできればいいなーって話

さてまずは、C#のプロパティーの話をします。
C#では、フィールドをpublicにするのは良くなくて、プロパティーを使ってフィールドにアクセスするのが良いとされています。

↓のコードは良くなくて

public class Launcher {
    // publicなフィールドだからこれは良くない
    public float speed;
}

↓な感じでプロパティーにするのがGoodです。

public class Launcher {
    float speed;

    // ちょっと古い書き方のプロパティー
    public float Speed {
        get { return speed; } 
        set { speed = value; }
    }
}

さて、↑みたいなコードはわざわざフィールドを作らなくても、↓のような自動実装プロパティーで実現ができます。

public class Launcher {
    public float Speed { get; set; }
}

自動実装プロパティーを使うことで、C#のコードからは見えない次のようなフィールドとアクセッサが、"自動"で生成されます。

  • <Speed>k__BackingFieldというフィールド
  • get_Speedというメソッド
  • set_Speedというメソッド

自動実装プロパティー、便利ですね!


さて次はUnityのシリアライズとフィールドとプロパティーの話。

UnityにおいてMonoBehaviourのサブクラスやScriptableObjectのサブクラスなどで、次のようなpublicなフィールドに設定した値や参照は、シーンやプレファブにシリアライズされることがあります。

using UnityEngine;

public class Launcher : MonoBehaviour {
    // publicなフィールドだからこれは良くない
    public float speed;
}

ですが先にも説明したとおり、C#として↑のようなpublicなフィールドは良くなかったですね。
こういう場合は、SerializeField属性の出番です。SerializeField属性をつけたフィールドはpublicでなくても、シリアライズされます。

using UnityEngine;

public class Launcher : MonoBehaviour {
    [SerializeField] float speed;
}

さて、このフィールドに外部からアクセスするために、プロパティーを付けてあげましょう。(とりあえずゲッタープロパティーだけ)

using UnityEngine;

public class Launcher : MonoBehaviour {
    [SerializeField] float speed;
    public float Speed => speed;
}

うん、よくあるパターンです。 このUnityで「シリアライズするフィールド」と「プロパティー」というよくあるパターンをもっと簡潔にしたいな!というのがこの投稿の趣旨です。


先ほど説明した自動実装プロパティ、あれは使えないのでしょうか?

自動で実装される内部的なフィールド(バッキングフィールド)に、SerializeField属性をつけることができたら、実現できそうです。なんと、これ実現できます。

C# 7.3から自動実装プロパティの自動で実装される内部的なフィールド(バッキングフィールド)に属性を付与できるようになりました。

using UnityEngine;

public class Launcher : MonoBehaviour {
    [field:SerializeField]
    public float Speed => speed;
}

うん、簡潔!めでたしめでたし!
とは、ならないいんです・・・

自動実装プロパティのフィールド名は、「<Speed>k__BackingField」という名前です。困ったことに、この名前でシリアライズされてしまいます。次の画像みたいな感じで。

スクリーンショット 2020-12-04 1.06.25.png
スクリーンショット 2020-12-04 1.06.21.png

うーん、困った。


さて、ここでC# 9.0から加わったSourceGeneratorの出番です。(まだUnityじゃ使えません。将来的にこうできればいいなーって話)

C# Source Generatorは、ビルド時にC#のソースコードを生成する仕組みです。

  • メインのプロジェクトがビルドされる前にコード生成
  • コード生成するために必要な入力値はコンパイル時に必要
  • 出力結果は、プロジェクトの一部となる
  • IDEにおいて、生成したコードの宣言にジャンプもできる
  • ILではなくC#を生成するので、デバックがすごい楽
  • 既存のソースコードを上書きしたりけしたりすることはできない

このC# Source Generatorを使うことで、プログラマティカルにコード生成をすることができます。C# Source Generatorを使うことで、ボイラープレートのコードは非常に簡単になります。

さて、自分はC# Source Generatorをつかって、「フィールドに付与すると、そのフィールドのゲッタープロパティーを生成してくれるSource Generator」を作ってみました!

ソースコードはこちら! RyotaMurohoshi/PropertyGenerator

using System;
using PropertyGenerator;

public partial class Product
{
    [GetterProperty(PropertyName = "Identifier")]
    private readonly int id;

    [GetterProperty] private readonly string name;

    public Product(string name, int id)
    {
        this.name = name;
        this.id = id;
    }
}

↑みたいな感じで、idやnameというフィールドにGetterPropertyという属性をつけると、↓のようなプロパティが生成されます。

public partial class Product
{
    public int Identifier => this.id;
    public string Name => this.name;
}

このGetterPropertyを使って、次のようなボイラープレートなコードもC# Source Generator使ってすっきりさせてみましょう。

using UnityEngine;

public class Launcher : MonoBehaviour {
    [SerializeField] float speed;
    public float Speed => speed;
}

↑が、↓こうなります!プロパティーがいらなくなりました!すっきり!

using UnityEngine;

public class Launcher : MonoBehaviour {
    [SerializeField, GetterProperty] float speed;
}

あんまり嬉しくない?確かに1行だとそうですね。

これが↓みたいにたくさんあったらどうでしょう?

using UnityEngine;

public class Monster : ScriptableObject {
    [SerializeField] int maxHp;
    public int MaxHp => maxHp;

    [SerializeField] int maxMp;
    public int MaxMp => maxHp;

    [SerializeField] int attack;
    public int Attack => attack;

    [SerializeField] int defense
    public int Defense => defense;

    [SerializeField] int speed
    public int Speed => speed;
}

これが↑、↓こうなる!すっきりしましたね!
ちなみに↑は、一部実装が間違っている箇所にきがつきましたか!↑はうっかりミスをしてます。↓ならこういううっかりミスも防げますね!

using UnityEngine;

public class Monster : ScriptableObject {
    [SerializeField, GetterProperty] int maxHp;
    [SerializeField, GetterProperty] int maxMp;
    [SerializeField, GetterProperty] int attack;
    [SerializeField, GetterProperty] int defense
    [SerializeField, GetterProperty] int speed
}

Unityで「シリアライズするフィールド」と「プロパティー」ですが、残念ながら自動実装プロパティはつかえません。そこで、Unityで「シリアライズするフィールド」と「プロパティー」を簡潔に書きたくて、SourceGeneratorを作ってみました。フィールドにつけるとゲッタープロパティーを生成するという非常に簡潔なものです!

よかったらコードを見てみてください! : RyotaMurohoshi/PropertyGenerator

これがUnityで使えるようになるのが楽しみです!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
7