Unityにおいて古いC#しか使えない時代もありました。しかし、それは過去のことです。本稿執筆時の最新LTSであるUnity 2019.4ではC# 7.3がサポートされています。また、本稿執筆時の最新Beta版であるUnity 2020.2ではC# 8.0がサポート予定です。
長らくUnityで古いC#しか使えなかったことで、「C#にこんな機能あるのか?知らなかった!」となることがある方も多いのではないでしょうか?この「Update your C# in Unity」シリーズでは、「C#の比較的新しい機能をUnityでこんな風に使えるよ!」という紹介を行います。
言語機能名: 式形式の関数メンバー
追加バージョン: C# 6.0で新規追加、C# 7.0で可能なメンバー追加
説明: メソッドやプロパティーなどのメンバーの実装が単一の式の場合、より簡潔にそのメンバーを記述できる機能
C#でコードを書いていると、単一のメソッドを呼び出しているだけのメソッドを書く時があります。例えば、次のようにStartCoroutineを呼び出してCoroutineを返すメソッドです。
using System.Collections;
using UnityEngine;
public class Launcher : MonoBehaviour
{
public Coroutine Launch()
{
return StartCoroutine(LaunchImpl());
}
private IEnumerator LaunchImpl()
{
// 略
yield break;
}
}
このようにメソッドの実装が単一の式で記述されている場合、より簡潔に式形式でメソッドを記述することができます。
using System.Collections;
using UnityEngine;
public class Launcher : MonoBehaviour
{
// 式形式で記述
// return や { や }がいらない
public Coroutine Launch() => StartCoroutine(LaunchImpl());
private IEnumerator LaunchImpl()
{
// 略
yield break;
}
}
return
や{
、}
は処理の本質ではないボイラープレートな記述です。式形式の関数メンバーを活用することで、処理の本質のみを記述した簡潔な記述になりました。
ちなみに次のように、返値がvoidなメソッドも記述できます。
using UnityEditor;
public static class AssetUpdater
{
[MenuItem("Assets/ForceReserializeAssets")]
private static void ForceReserializeAssets() => AssetDatabase.ForceReserializeAssets();
}
式形式の関数メンバーの使い所として多いのは、単純な処理で実装されたToStringメソッドとゲッターオンリーのプロパティです。
[Serializable]
public class Circle
{
[SerializeField]
private float x;
[SerializeField]
private float y;
[SerializeField]
private float radius;
public Circle(float x, float y, float radius)
{
this.x = x;
this.y = y;
this.radius = radius;
}
// フィールドをそのまま返すのにも使える
public float X => x;
public float Y => y;
public float Radius => radius;
// ロジックを記述したプロパティにも使える
public float Area => Mathf.PI * radius * radius;
// ToStringの実装にも使える
public override string ToString() => $"Center ({X},{Y}) Radius:{Radius}";
}
ゲッター・セッター両方あるプロパティーやインデクサーにも使えます。
[Serializable]
public class State
{
[SerializeField] private int score;
public State(int score)
{
this.score = score;
}
public int Score
{
get => score;
set => score = value;
}
}
public class Dungeon
{
private int[][] map;
public int this[int i, int j]
{
set => map[i][j] = value;
get => map[i][j];
}
}
「別に短くなっても対して嬉しくないんじゃないか?」という疑問を持った方もいるかもしれません。
その疑問への回答は、「短いメンバーがずらっとたくさん並んだ時に、式形式のメンバーで記述すると、非常に短くなって嬉しい」というものです。
例えば、Vector2のいくつかのオペレーターを、従来どおりの書き方で実装すると、おそらくこのようになるでしょう。
public static Vector2 operator +(Vector2 a, Vector2 b) {
return new Vector2(a.x + b.x, a.y + b.y);
}
public static Vector2 operator -(Vector2 a, Vector2 b) {
return new Vector2(a.x - b.x, a.y - b.y);
}
public static Vector2 operator *(Vector2 a, Vector2 b) {
return new Vector2(a.x * b.x, a.y * b.y);
}
public static Vector2 operator /(Vector2 a, Vector2 b) {
return new Vector2(a.x / b.x, a.y / b.y);
}
public static Vector2 operator -(Vector2 a) {
return new Vector2(-a.x, -a.y);
}
public static Vector2 operator *(Vector2 a, float d) {
return new Vector2(a.x * d, a.y * d);
}
public static Vector2 operator *(float d, Vector2 a) {
new Vector2(a.x * d, a.y * d);
}
public static Vector2 operator /(Vector2 a, float d) {
return new Vector2(a.x / d, a.y / d);
}
{
と}
でかなりスペースをとっています。
式形式でメンバーを記述することで嬉しいのは、このようにメンバーがずらっと並んだ時です。本質でない「return
や{
、}
」で行数やスペースを取らなくなります。
これはこんな感じで短くなります。
public static Vector2 operator +(Vector2 a, Vector2 b) => new Vector2(a.x + b.x, a.y + b.y);
public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.x - b.x, a.y - b.y);
public static Vector2 operator *(Vector2 a, Vector2 b) => new Vector2(a.x * b.x, a.y * b.y);
public static Vector2 operator /(Vector2 a, Vector2 b) => new Vector2(a.x / b.x, a.y / b.y);
public static Vector2 operator -(Vector2 a) => new Vector2(-a.x, -a.y);
public static Vector2 operator *(Vector2 a, float d) => new Vector2(a.x * d, a.y * d);
public static Vector2 operator *(float d, Vector2 a) => new Vector2(a.x * d, a.y * d);
public static Vector2 operator /(Vector2 a, float d) => new Vector2(a.x / d, a.y / d);
一覧した際に非常に短くなりましたね。
ちなみにコンストラクターでも活用できます。「読み取り専用の自動プロパティ」と「タプルの生成・分解」と組み合わせてこんな感じもかけます。(バリュータプルを使っているけど、内部的にはバリュータプルは生成されません)マイクロソフトの公式ドキュメントではたまにこの書き方を見かけます。
public class Point {
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
// 内部的にはこんな感じ
// バリュータプルを使っているけど、内部的にはバリュータプルは生成されませんhttps://sharplab.io/#v2:EYLgtghgzgLgpgJwDQBMQGoA+ABATARgFgAoE7AZgAI9KAFAewEsA7GSgbxMu+qpbYAaHSgHM4MANyUAvlx4VK/SgE1hYyTLnctvOk1YAKJQA8ki1pQCeASkoBeAHyUDAs8tt3npq9YklZxEA===
//public class Point
//{
// [CompilerGenerated]
// [DebuggerBrowsable(DebuggerBrowsableState.Never)]
// private readonly int <X>k__BackingField;
//
// [CompilerGenerated]
// [DebuggerBrowsable(DebuggerBrowsableState.Never)]
// private readonly int <Y>k__BackingField;
//
// public int X
// {
// [CompilerGenerated]
// get
// {
// return <X>k__BackingField;
// }
// }
//
// public int Y
// {
// [CompilerGenerated]
// get
// {
// return <Y>k__BackingField;
// }
// }
//
// public Point(int x, int y)
// {
// <X>k__BackingField = x;
// <Y>k__BackingField = y;
// }
//}
式形式の関数メンバーを使うと、メソッドやプロパティーなどのメンバーの実装が単一の式の場合、より簡潔にそのメンバーを記述することができます。
「別に短くなっても対して嬉しくないんじゃないか?」という疑問を持った方もいるかもしれません。
しかし「短いメンバーがずらっとたくさん並んだ時に、式形式のメンバーで記述すると、非常に短くなって嬉しい」です。
ぜひ活用してください。