【C#】面倒くさがり屋がリフレクションを使ってEnumに対応したメンバ変数を返すようにした話

# 最初に

どうも、ろっさむです。

今回はユーザー定義したEnumの名前に対応したstring型のメンバ変数をリフレクションで適宜返すようなメソッドを作った話をします。

使いどころとしては、例えば顔のblendshape値を弄る時に表情毎の顔パーツそれぞれblendshape名を取得して諸々処理を掛ける時に便利かなと。

# 前提コード

```C#
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;

[System.Serializable]
public class FacialData
{
    // 以下メンバ変数にはblendshape名が含まれる
    public string BrwMorph;
    public string EyeMorph;
    public string NoseMorph;
    public string MouthMorph;

    public FacialData(
        string brwMorph,
        string eyeMorph,
        string noseMorph,
        string mouthMorph)
    {
        BrwMorph = brwMorph;
        EyeMorph = eyeMorph;
        NoseMorph = noseMorph;
        MouthMorph = mouthMorph;
    }
}
```



```c#
public class FacialExpressionManager : MonoBehaviour
{

    private enum MorphType
    {
        Brw,
        Eye,
        Nose,
        Mouth,
    }
    
    private void UpdateMorphWeight(FacialData data, MorphType morphType)
    {
        // ここでblendshape名を取得したい…
        // var morphPath = ?
            
        // 後はなんやかんやパスを使った処理
        ...
            
    }
    
    private void Update()
    {
        // 表情名から対応したFacialDataを取得する
        FacialData newFacialData = FacialData.GetFacialData(facialExpressionName);
        foreach (MorphType type in Enum.GetValues(typeof(MorphType)))
        {
            UpdateMorph(newFacialData, type);
        }
    }
}

```



# 実装

blendshape名を取得するためにswitchやらifを使うと後からEnumなどにパラメータが増えたときに付け足すコードが増える上に、どんどん分岐が長くなってしまうのでそれを避けるためにリフレクションという方法を考えてみました。

```C#
public class FacialExpressionManager : MonoBehaviour
{

    private enum MorphType
    {
        Brw,
        Eye,
        Nose,
        Mouth,
    }
    
    private void UpdateMorphWeight(FacialData data, MorphType morphType)
    {
        // ここでblendshape名を取得したい
        // Enumの値の文字列を含むFacialDataのメンバ変数が欲しい
        var morphPath = data.GetMorphPath(morphType.ToString());
            
        // 後はなんやかんやパスを使った処理
        ...
            
    }
}
```



`GetMorphPath`は以下のような実装にしました。

```C#
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;

[System.Serializable]
public class FacialData
{
    // 以下メンバ変数にはblendshape名が含まれる
    public string BrwMorph;
    public string EyeMorph;
    public string NoseMorph;
    public string MouthMorph;

    public FacialData(
        string brwMorph,
        string eyeMorph,
        string noseMorph,
        string mouthMorph)
    {
        BrwMorph = brwMorph;
        EyeMorph = eyeMorph;
        NoseMorph = noseMorph;
        MouthMorph = mouthMorph;
    }
    
    public string GetMorphPath(string name) => GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).Where(property => property.Name.Contains(name)).Select(property => property.GetValue(this) as string).SingleOrDefault();
   
}
```

順を追って `GetMorphPath`の処理を見ていきましょう。

1. まず `GetType()`で自身の型宣言を取得します。
2. `GetFields`でメンバを取得しますが、その際に `public`でありインスタンスメンバーを検索に含めるようにします。
3. LINQの`Where`を使って取得したメンバ群から、メンバ名の中で渡したEnumの名前を含むものだけになるようフィルタリングします。
4. LINQの`Select`でフィルタリング後のメンバ変数の値をstring型にキャストします。
5. 最後の`SingleOrDefault()`で最終的に条件に合った単一のメンバ変数を返すか、それがなかった場合は型のデフォルト値を返すようにします。ちなみに複数見つかった場合は例外が発生します。



これで`Enum`の値と増えるメンバ変数に合わせた処理ができました。



ただ、このテクニックは使う場面がかなり限られそうです…。