C# で別のクラス・関数を文字列から呼び出す

# はじめに

プログラムを書いているだけだと特殊ケースではあるが、やはり特にスクリプト処理をパイプラインとして用意している場合などは、ダイナミックに関数の設定・呼び出しを行えると嬉しい。
特にその場合の呼び出し設定は文字列の場合も多い。

では、C# の場合、どうやって呼び出すのか？というのが気になった。

### 実行環境

* Windows 11

# 実コード

## 呼び出し先

実行として呼び出し対対象のコードを簡単に用意しておく。

```csharp
public class CallFunctions
{
    // Start is called before the first frame update
    public void SayHello()
    {
        Debug.Log("Hello, World!");
    }
}
```

## 呼び出し元

```csharp
public class Call
{
    public static void CallFunction()
    {
        Type callFunc = Type.GetType("CallFunctions");
        ConstructorInfo callFuncConstructor = callFunc.GetConstructor(Type.EmptyTypes);
        object callFuncClassObject = callFuncConstructor.Invoke(new object[] { });

        MethodInfo sayHelloMethod = callFunc.GetMethod("SayHello");
        object callValeu = sayHelloMethod.Invoke(callFuncClassObject, new object[] { });
    }
}
```

順序としては、

1. Type 型を使って、クラスオブジェクトを `Type.GetType(<クラス名>)` として呼びだす
2. コンストラクタを取得する
3. コンストラクタを呼び出す
4. メソッド情報を取得する
5. メソッドを呼び出す

といった流れ。
特にここでの重要なポイントはコンストラクタの呼び出しを忘れないこと。
これを忘れると、

```
error CS0103: The name 'callFuncClassObject' does not exist in the current context
```

としてエラーが返ってきてしまう。

## ネームスペース下でのアクセス

例えば、 `namespace Sample {}` 下に呼び出し先があるとする。
この場合は、 `Type.GetType({ClassName})` の ClassName 側を `{Namespace}.{ClassName}` として渡す必要がある。

### 呼び出し先

```diff_c_sharp
+ namespace Sample {
    public class CallFunctions : MonoBehaviour
    {
        // Start is called before the first frame update
        public void SayHello()
        {
            Debug.Log("Hello, World!");
        }
    }
+ }
```

### 呼び出し元

```diff_c_sharp
public class Call
{
    public static void CallFunction()
    {
-       Type callFunc = Type.GetType("CallFunctions");
+       Type callFunc = Type.GetType("Sample.CallFunctions");
        ConstructorInfo callFuncConstructor = callFunc.GetConstructor(Type.EmptyTypes);
        object callFuncClassObject = callFuncConstructor.Invoke(new object[] { });

        MethodInfo sayHelloMethod = callFunc.GetMethod("SayHello");
        object callValeu = sayHelloMethod.Invoke(callFuncClassObject, new object[] { });
    }
}
```

## 別アセンブリからのアクセス

呼び出し元のコードが `A` アセンブリに存在し、かつ、呼び出し元が `B` アセンブリに存在する場合。
ここでは、 `Type.GetType()` に対して `{Namespace}.{ClassName},{AssemblyName}` (コンマと要素間のスペースはあってもなくても良い。) の文字列を渡す。

### 呼び出し先

```diff_c_sharp:Assembly: A
namespace Sample {
    public class CallFunctions : MonoBehaviour
    {
        // Start is called before the first frame update
        public void SayHello()
        {
            Debug.Log("Hello, World!");
        }
    }
}
```

### 呼び出し元

```diff_c_sharp:Assembly: B
public class Call
{
    public static void CallFunction()
    {
-       Type callFunc = Type.GetType("Sample.CallFunctions");
+       Type callFunc = Type.GetType("Sample.CallFunctions, B");
        ConstructorInfo callFuncConstructor = callFunc.GetConstructor(Type.EmptyTypes);
        object callFuncClassObject = callFuncConstructor.Invoke(new object[] { });

        MethodInfo sayHelloMethod = callFunc.GetMethod("SayHello");
        object callValeu = sayHelloMethod.Invoke(callFuncClassObject, new object[] { });
    }
}
```

# 最後に

ぐぐりながらも出てきた結果を試してみたが、以外とコンストラクタの呼び出しを忘れて、そのクラスオブジェクト見つからないよと言われて沼ることがあった。
ここら辺は一個のお作法として覚えたいところ。

# 参考資料

https://stackoverflow.com/a/51654820