【Advent Calendar 2016 Unity2】Unity5.6 x C#6

Advent Calendar 2016 Unity2

C#大好きてんぷらです。ただ最近はUnityのC#を使うことがほとんどで、UnityのC#はコンパイラが古いためにC#4までしか使えません。
しかし、Unity5.5でついにコンパイラがバージョンアップしました!これでUnityでも近い将来C#6まで使えるようになるはずです☆

Unityの公式サポートはまだC#4ですが、コンパイラがバージョンアップしたことで、無理くりすれば現状のUnityでもC#6の機能が使えるようになります♪
ここではUnity5.6でC#6の機能を試してみた記録を記します(自分が気になった機能だけの紹介になります)。
※当初Unity5.5で試してたのですが、途中でUnity5.6βが使えるようになったのでバージョンアップしました。Unity5.6でもC#4までしか使えないようです。

環境構築

【++C++; // 未確認飛行 C】Unity 5.5でasync/await使えた話

きっかけはいつもお世話になってる「++C++; // 未確認飛行 C」さんのこの記事。
記事にあるように、Assets配下に「mcs.rsp」ファイルを作り、-langversion:6の記述を追加することで、すんなりC#6が認識されました。
人によってはIDEの設定とかあるかもですが、自分は Visual Studio for Mac を使っていて、特に必要なかったです。

注意点として、あくまでコンパイラがバージョンアップしただけで、標準ライブラリやUnityが対応しないとエラーになる機能は使えません。
例えばdynamicキーワードは定義したら以下のエラーが出ました。

Assets/Scripts/Scene/Demo.cs(11,4): error CS1980: 
Dynamic keyword requires `System.Runtime.CompilerServices.DynamicAttribute' to be defined. 
Are you missing System.Core.dll assembly reference?

DynamicAttributeがないとか。あとC#6のusing staticはUnityエディタの方でエラーが出力されていました。

今回はWebGLでビルドして試したところ、特にエラーもなく使えてるようでした。IL2CPPが動作するかは未検証です。

自動プロパティの拡張

自動プロパティはアクセサの記述を省略出来る機能ですが、C#6からは以下が行えるようになりました。

初期化子

初期値が設定できるようになりました。

using UnityEngine;
using System.Collections.Generic;

namespace MerryChristmas
{
    public class Monster : MonoBehaviour
    {
        // 今まで
        public bool IsAlive { get; set; }
        public List<int> ItemIds { get; set; }

        void Start()
        {
            IsAlive = true;
            ItemIds = new List<int>();
        }

        // C#6
        public bool IsAlive { get; set; } = true;
        public List<int> ItemIds { get; set; } = new List<int>();
    }
}
getterのみの自動プロパティ

getterのみの自動プロパティが使えるようになりました(初期値が設定できるようになったので)。

using UnityEngine;

namespace MerryChristmas
{
    public enum CharaType { Player, Monster, NPC }

    public class Monster : MonoBehaviour
    {
        // 今まで
        public CharaType Type { get; private set; }

        void Start()
        {
            Type = CharaType.Monster;
        }

        // C#6
        public CharaType Type { get; } = CharaType.Monster;
    }
}

私見
自分は今まで自動プロパティを敬遠してきました。何度か試したりはしてきたのですが、どうしても使いづらく。。
原因としては、初期値を指定出来ないから。初期値指定が必要な場合、メンバ変数を用意するかコンストラクタで設定が必要でした。メンバ変数を用意すると無駄な変数が増えるし、コンストラクタに書くとコードが分離するという問題がありました。Unityだと、MonoBehaviourはコンストラクタを使えないのでStartメソッド等に書くことになり、もっと分かりづらいことになってました(結果最初からメンバ変数を用意した方が無難という結論に)。

C#6からは自動プロパティの需要が増えると思います。Unityはメンバ変数をエディタから参照出来るので、全部が全部プロパティになるかと言われたらそうではないですが、エディタから参照しない外部に公開するメンバ変数 → 自動プロパティという感じになるんじゃないかなーと漠然と思ってます。

Unityだとエディタ上から値を確認するためにわざとメンバ変数にしたりもするので、メンバ変数にするかプロパティにするかのバランスが難しくなりそう。単純にコードを綺麗にするなら自動プロパティを使った方がいいのだろうけど・・・。

Expression-Bodied Function Members

メソッドやプロパティの記述が1つで済む場合に => を使った簡易文法で書けるようになりました(プロパティはgetterのみの場合に使用可)。

using UnityEngine;

namespace MerryChristmas
{
    public enum CharaType { Player, Monster, NPC }

    public class Monster : MonoBehaviour
    {
        // 今まで
        CharaType type = CharaType.Monster;

        public CharaType Type { get { return type; } }
        public int Id { get; private set; }

        public void Init(int id)
        {
            Id = id;
        }

        // C#6
        public CharaType Type => CharaType.Monster;
        public int Id { get; private set; }

        public void Init(int id) => Id = id;
    }
}

私見
これはコード量が激減するなぁ。特にgetterのみのプロパティで使った場合が強力。どうしてもプロパティの記述が間延びしがちだったから、見た目がスマートになって良い♪
自動プロパティの初期化子とExpression-Bodied Function Membersを併用すれば相当なコード量の削減になりそう。

null条件演算子

nullでなければ〜、みたいなよくやるnull判定を ?. 演算子で記述出来るようになりました。

using UnityEngine;
using System;

namespace MerryChristmas
{
    public class Demo : MonoBehaviour
    {
        [SerializeField]MonsterParty party;

        void Start()
        {
            // 先頭モンスターのID取得
            int? id = party?[0]?.Id;
            if(id.HasValue)
            {
                Debug.Log("Id is " + id.Value);
            }
            else
            {
                Debug.Log("Not Found");
            }

            // 先頭モンスターのアニメーション実行
            party?[0]?.Animation?.Run?.Invoke();
        }
    }

    public class MonsterParty : MonoBehaviour
    {
        public Monster this[int idx] => idx < Monsters?.Length ? Monsters[idx] : null;
        public Monster[] Monsters { get; set; }
    }

    public class Monster : MonoBehaviour
    {
        public int Id { get; set; }
        public MonsterAnimation Animation { get; set; }
    }

    public class MonsterAnimation : MonoBehaviour
    {
        public Action Run { get; set; }
    }
}

私見
null条件演算子が追加されたことでNullable型の需要が増えるかも。
Action等を使用したコールバックメソッドはnull判定をすることが多くて、Invokeメソッドとnull条件演算子を使えば記述が短くなって嬉しい。

あと試してて疑問に思ったのが、GameObjectのnull判定について。
【Unity】DestroyされたGameObjectはnullなのか
UnityがC#6に対応した場合にnull条件演算子の挙動はどうなるんだろう・・・。一応、現状で試してみたらGameObjectが破棄された後にエラーも出ずにnull条件演算子使用以降のコードが処理されなくなってしまった(予期せぬ挙動をしてる)。

文字列挿入

文字列をフォーマットする場合、今まではstring.Formatを使用してましたが、文字列内に直接値を書ける機能が追加されました。

using UnityEngine;

namespace MerryChristmas
{
    public class Demo : MonoBehaviour
    {
        void Start()
        {
            // 今まで
            var path    = string.Format("Prefabs/{0}", typeof(Monster).Name);
            var monster = Instantiate(Resources.Load<Monster>(path));

            // C#6
            var path    = $"Prefabs/{typeof(Monster).Name}";
            var monster = Instantiate(Resources.Load<Monster>(path));
        }
    }

    public class Monster : MonoBehaviour { }
}

私見
より直感的なコードになる。ただシステム的に普遍なフォーマット文字列以外はちゃんと定数切ってstring.Formatした方が良いとは思う。

nameof演算子

変数、型、クラス、メソッド、プロパティ等の名前を取得出来る演算子が追加されました。

using UnityEngine;

namespace MerryChristmas
{
    public class Demo : MonoBehaviour
    {
        void Start()
        {
            // 今まで
            var path    = string.Format("Prefabs/{0}", typeof(Monster).Name);
            var monster = Instantiate(Resources.Load<Monster>(path));

            // C#6
            var monster = Instantiate(Resources.Load<Monster>($"Prefabs/{nameof(Monster)}"));
        }
    }

    public class Monster : MonoBehaviour { }
}

私見
個人的にお気に入りな機能♪UnityだとPrefabを多用するけど、Prefab名=Component名になってることが多い。そういう場合、自分は、typo防止やエラー検出のためにtypeof(***).Nameを使用してました。
そう、自分が欲しかったのはnameofですよ(笑)

using static

using staticを使用することで、静的メンバへのアクセス時に型名を省略出来るようになりました。

using UnityEngine;
using static UnityEngine.Debug;          // 標準ライブラリ
using static UnityEngine.SystemLanguage; // enum
using static MerryChristmas.Const;       // クラス(静的クラスじゃなくてもOK)

namespace MerryChristmas
{
    public class Demo : MonoBehaviour
    {
        void Start()
        {
            // 今まで
            if(Const.IsEditor)
            {
                var language = SystemLanguage.English;
                if(language == SystemLanguage.Japanese)
                {
                    Debug.Log("Japanese");
                }
                else if(language == SystemLanguage.English)
                {
                    Debug.Log("English");
                }
            }

            // C#6
            if(IsEditor)
            {
                var language = English;
                if(language == Japanese)
                {
                    Log("Japanese");
                }
                else if(language == English)
                {
                    Log("English");
                }
            }
        }
    }

    public class Const
    {
        public static readonly bool IsEditor = Application.platform == RuntimePlatform.OSXEditor;
    }
}

私見
定数定義クラスやUtilクラス等に使用すると良いかも♪あと地味にenumの記述を短く出来るのが嬉しい。
しかしこちらの機能はUnityエディタの方でInspectorにエラーが表示されてまだ使えませんでした(Monobehaviourが機能しないよう)。

最後に

今回はC#6に限った話しですが、もちろんC#5の機能も使えるようになるはず。標準ライブラリがどこまで対応されるかだけど、Taskが実装されたら使ってみたいな〜。
Unityの正式対応が本当に待ち遠しい〜(>_<)☆

Advent Calendar 2016 Unity2