【Unity】【C#】dynamicキーワードを使って変数やメソッドを動的に操作する

dynamicキーワードを使って変数やメソッドを動的に操作する方法です。

Unity2018.3.6f1
Scripting Runtime Version : .Net 4.x Equivalent
Api Comptibility Level : .Net 4.x

iOSのIL2CPPビルドではまだ使えない

dynamicは内部的にExpression Treeを使っています。
そしてExpression TreeはiOS + IL2CPPでビルドするとランタイムエラーになります(本記事作成時点では)。
これについては次の記事を参照してください。

light11.hatenadiary.com

エディタ拡張ではもちろん使えます。

dynamic?

dynamicはC#4.0から追加されたキーワードで、変数やメソッドに動的に(文字列で)アクセスできます。
要するに、こんなことができます。

// 適当にMonoBehaviourを取得(何でもいい)
MonoBehaviour behaviour = GetComponent<MonoBehaviour>();
        
// MonoBehaviourをdynamicな変数に格納する
dynamic dynamicBehaviour = behaviour;

// MonoBehaviourに定義されていない任意の名前のメソッドを呼べる
// (普通はコンパイルエラーになる)
dynamicBehaviour.SomeMethod();

上記のコードは、GetComponent()したMonoBehaviourのサブクラスにSomeMethod()が定義されていれば、正常にそのメソッドが呼ばれます。

dynamicを使わないでこれを実現する場合はリフレクションやExpression Treeを使う必要がありました。
dynamicはこれらの代替手段と言えそうです。

ちなみにdynamicは内部的にはExpression Treeを使っているとのことです。

ufcpp.net

Expression Treeと同じ程度(ちょい悪いくらい)のパフォーマンスで、Expression Treeよりもずっと簡潔に書けるためメリットは大きそうです。
もちろんリフレクションよりはずっとパフォーマンスが良いです。

Unityとdynamic

さてUnityでdynamicを使うには、Scripting Runtime Versionを.Net 4.x Equivalentにする必要があります。

f:id:halya_11:20190226212153p:plain

この設定によりdynamicを使ったソースコードを記述できるようになります。
ただし、Unity2018.3.6ではコンパイル時に下記のエラーが出てしまいました。

error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

これを解決するにはApi Compatibility Levelを.Net 4.x`に上げる必要があります。

f:id:halya_11:20190226212434p:plain

これでコンパイルエラーも出なくなりました。

Reflectionの代わりに使う

それでは実際にdynamicキーワードを使ってみます。
古いバージョンではReflectionを使わないと実現できなかったようなケースをdynamicで実現してみます。

using UnityEngine;

public class DynamicExample : MonoBehaviour
{
    [SerializeField]
    private MonoBehaviour _behaviour;

    private void Start()
    {
        dynamic example = _behaviour;

        // メソッド
        example.Example();

        // フィールド
        Debug.Log(example.exampleField);

        // プロパティ
        Debug.Log(example.ExampleProp);
    }
}

このSerializeFieldに下記のコンポーネントの参照を持たせます。

using UnityEngine;

public class DynamicReceiver : MonoBehaviour
{
    private int exampleField = 2;

    public float ExampleProp { get; set; } = 5.0f;

    public void Example()
    {
        Debug.Log("example");
    }
}

無事実行されることが確認できました。

ちなみに、変数やメソッドが定義されていない場合には例外が発生し、
さらに変数がメソッドがprivateでも例外が発生しました。

jsonxmlへのアクセスも簡易的に書ける

またdynamicを使うとjsonxmlの取り扱いが非常にシンプルで直観的に書けます。
Dictionary等を使って煩雑に書く必要も、JsonUtilityを使って型を定義する必要もありません。
ただ、System.Dynamic.DynamicObjectを継承したクラスを作る必要はあります。

このあたりは素晴らしいライブラリがあるようなのでリンクで済ませてしまいます。

neue.cc

参考

ufcpp.net

ufcpp.net

neue.cc

関連

light11.hatenadiary.com

light11.hatenadiary.com