※ この投稿で紹介する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
」という名前です。困ったことに、この名前でシリアライズされてしまいます。次の画像みたいな感じで。
うーん、困った。
さて、ここで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で使えるようになるのが楽しみです!