C# 7.3から自動実装プロパティのバッキングフィールドに属性をつけられるようになりました。
前半はこれについて解説し、後半はこれを用いてUnityでこんなことできればよかったのにな、ということを紹介します。
プロパティ?自動実装プロパティ?バッキングフィールド?
まずC#におけるプロパティ。こんなやつですね。
using System;
public class Example
{
int field;
public int MyProperty
{
get { return field; }
private set { field = value; }
}
}
つづいて、こちらは自動実装プロパティ。
using System;
public class Example
{
public int MyProperty { get; private set; }
}
さて、こちらの自動実装プロパティ。コード上にフィールドはありません。しかし、内部的にコンパイラーがフィールドを生成しています。
MyPropertyというプロパティがある場合、コンパイラーはC#のコードからは見えないフィールドとメソッドを作成します。
現在のRoslyn Compilerは、
- <MyProperty>__BackingFieldというバッキングフィールド
- get_MyPropertyというアクセッサ
- set_MyPropertyというアクセッサ
というフィールドとメソッドを生成します。
「自動実装プロパティのバッキングフィールド」とは、コードからは見えない、コンパイラーが生成した、プロパティを実現するうえで必要なフィールドです。
ちなみにバッキングフィールドは、「<」という名前で始まります。C#のフィールド名としては、「<」は無効です。ILとしてのフィールド名としては、「<」は有効です。
C# 7.3から属性をつけられるようになった
「自動実装プロパティのバッキングフィールド」とは、コードからは見えない、コンパイラーが生成した、プロパティを実現するうえで必要なフィールドでしたね。
C# 7.3より前では、この「自動実装プロパティのバッキングフィールド」に対して、属性(アトリビュート)を付与することができませんでした。これができるようになって何がうれしいのか。
例えば、次のコードはC# 7.3でより簡潔に書けるようになります。
これがC# 7.3より前のコード。
using System;
[Serializable]
public class Example
{
[NonSerialized]
int field;
public int MyProperty
{
get { return field; }
private set { field = value; }
}
}
- ExampleクラスはSerializable属性が付与されている
- MyPropertyはfieldを利用
- fieldはNonSerialized属性が付与されている
となっています。
これはシリアライズされるExampleクラスの内、fieldをシリアライズ対象から外したいためにこのような実装にしています。
このコードが、C# 7.3からより簡潔に書けるようになりました。
using System;
[Serializable]
public class Example
{
[field: NonSerialized]
public int MyProperty { get; private set; }
}
C# 7.3からは次のように記述することができます。C# 7.3からは、自動実装プロパティのバッキングフィールドに対して属性を付与することができるため、MyPropertyで使っている内部のフィールドをシリアライズ対象から外すことができるようになりました。
C# 7.3より前では、自動実装プロパティのバッキングフィールドに属性を付与することはできませんでした。そのため、自動実装プロパティをあきらめて普通のプロパティにするしかないことがありました。
ちなみに、[field: XxxAttribute]
という記法ですが、これはC# 1.0のころからevent用に存在していたらしいです。
Unityでこんなことできたらよかったのに
ここまではC# 7.3で自動実装プロパティのバッキングフィールドに属性をつけられるようになった話。
ここからは「これを使って、Unityでこんなことができたらよかったのに」という話。
次はUnityでよくあるSerializeField属性を付与したフィールド、そしてそれを使ったプロパティというコードです。
これでlevelフィールドはシリアライズされ、プロパティとしてPlayerの外部からアクセスすることができます。
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
int level;
public int Level {
get { return level; }
private set { level = value; }
}
}
これがよく書くんだけど、長い。もう少し短くしたい。
「C# 7.3で自動実装プロパティのバッキングフィールドに属性をつけられるようになった」のだから、できれば次のように書きたい!
using UnityEngine;
public class Player : MonoBehaviour
{
[field: SerializeField]
public int Level { get; private set; }
}
ただ、残念ながらうまくいかない。
理由は、
- Unityはフィールド名でシリアライズするから
- Levelのバッキングフィールドは「<Level>__BackingField」という名前だから
そのため次の画像のように、データ(YAML形式)は次のような名前でシリアライズされます。
そして、Unityのエディター上では次のように表示されます。
うまくいかない。
Unity、もしくはC#にこんな機能が欲しい
さて、やっぱりこんな感じで書きたいわけです。短く。
using UnityEngine;
public class Player : MonoBehaviour
{
[field: SerializeField]
public int Level { get; private set; }
}
そこで取れる方法は2つですね。
1つ目。Unityにシリアライズする際の名前を指定する機能を入れてもらう。
そして、こんな感じで書けるようにしてもらう。
using UnityEngine;
public class Player : MonoBehaviour
{
[field: SerializeField]
[field: SerializeAs("level")]
public int Level { get; private set; }
}
もしくはこんな感じ。
using UnityEngine;
public class Player : MonoBehaviour
{
[field: SerializeField("field")]
public int Level { get; private set; }
}
ちなみに、こちらはUnity Feedbackにもう上げています。もし、賛同してもらえる方は投票をお願いします。
Unity Feedback : FEATURE THAT SPECIFIES SERIALIZED NAME NOT WITH FIELD NAME.
2つ目。
C#の方に、自動実装プロパティのバッキングフィールドの名前を指定する機能を入れる。
これはプロポーザルを上げるとか、プルリクエストを送ればいいはず。ただ、これあんまり需要がなさそう。実現性高くないかも。
まとめ
- C# 7.3から自動実装プロパティのバッキングフィールドに属性をつけられるようになった
- Unityでこれをいい感じに使いたかったけど微妙だった
- Unityにシリアライズ名を指定する機能、追加されないかな
- C#のプロパティのバッキングフィールドを指定する機能、つけることってできないかな