.NET Core で特定のインターフェイスを実装したクラスを探して実行する

.Net Core で、特定のインターフェイス実装を探して、実行するというプログラムを書きたいと思って調査した。実際のユースケースは、プロバイダを拡張可能にしたくて、Azure Functions の　DIを一か所ではなく、プロバイダごとに書くようにしたかったのだが、Azure Functions ではどうやら、Startupクラスは１つまでしか認識しないっぽいので、自分で委譲するプログラムを書くことにした。

# インターフェイス実装クラス

別の　namespace にインターフェイス実装したクラスを作成する。インターフェイスは、パラメータありのパターンと無しのパターンを用意した。どちらも同一のプロジェクトにいるので、同じアセンブリになる。

_ITalkable interface and Hello class_

```csharp
namespace FindInterfaceImplementation
{
    public interface ITalkable
    {
        void Talk();
        void Append(List<string> str);
    }

    public class Hello : ITalkable
    {
        public void Talk()
        {
            Console.WriteLine("Hello!");
        }

        public void Append(List<string> str)
        {
            str.Add("Hello!");
        }
    }
}
```

_world class_

```csharp
namespace FindInterfaceImplementation.World
{
    public class World :ITalkable
    {
        public void Talk()
        {
            Console.WriteLine("World!");
        }

        public void Append(List<string> str)
        {
            str.Add("World!");
        }
    }
}

```

# インターフェイスを実装したクラスの探索

上記のインターフェイスを実装したクラスを探索する。

## Assembly の探索

現在のコードが書かれたデフォルトのアセンブリでよければ`Microsoft.Extensions.DependencyModel`をnugetで取得して、`DependencyContext.Default.RuntimeLibraries`の名前が一致するものを調べる。参考にしたコードでは、そのDependencyがアセンブリ名で始まるものも対象としている。（なぜそうしているのか？はまだ理解できていません。）

```csharp
public static IEnumerable<Assembly> GetReferencingAssemblies1(string assemblyName)
{
    return DependencyContext.Default.RuntimeLibraries.Where(p => IsCandidateLibrary(p, assemblyName)).Select(
        p => Assembly.Load(new AssemblyName(p.Name)));
}
private static bool IsCandidateLibrary(RuntimeLibrary library, string assemblyName)
{
    return library.Name == assemblyName || library.Dependencies.Any(d => d.Name.StartsWith(assemblyName));
}
```

このコードを使うと、assemblyName で指定したアセンブリが取得できるようになります。Assemblyの`GetExportedType`メソッドで、public のクラスの型が取れますので、それが、ターゲットのインターフェイスを実装しているかを調査したら完了。多分Linqで書いたらもっとすっきりすると思います。

```csharp
var name = typeof(Program).Assembly.GetName().Name;
var assemblies = GetReferencingAssemblies1(name);
List<string> result = new List<string>();
foreach (var assembly in assemblies)
{
     foreach(var impl in assembly.GetExportedTypes().Where(p => p.GetInterfaces().Contains(typeof(ITalkable)))) 
     {
　　　　　　　// 対象のメソッド呼び出し
     }

}
```

# クラスの動的な実行

あとは、リフレクションで取得した対象のクラスのインスタンスを生成して実行すれば終了。先ほどのループの中身のみ抜き出します。`Activator.CreateInstance`でインスタンスを生成したのち、method.Invokeで単純にメソッドを実行します。リフレクションであっても、参照渡しになるので、List<string> であるresultに値が入って表示されます。

```csharp
var method = impl.GetMethod("Talk");
var obj = Activator.CreateInstance(impl);
method.Invoke(obj, null);
var method2 = impl.GetMethod("Append");
method2.Invoke(obj, new object[] {result});
```

## 実行結果

```
Hello!
World!
```

resultの中身を見てみると、しっかり値が入っています。

```csharp
 Console.WriteLine($"Result: {result[0]} {result[1]}");
```

```
Result: Hello! World!
```

# まとめ
.NetFramework と .NetCore でやり方が違う様子ですが、これで無事取得でしました。`DependencyContext.Load`などのメソッドを使うと、現在のライブラリ以外も検索できるので、拡張性を持ったライブラリなどを作るときには使えそうですね。

* [サンプルソース](https://github.com/TsuyoshiUshio/FindInterfaceImplDotNetCore)

## 参考にしたサイト

* [Create and load code at runtime in .NET Core](http://proemmer.azurewebsites.net/2016/10/06/create-and-load-code-at-runtime-in-dotnet-core/)
* [Replacing AppDomain in .Net Core](http://www.michael-whelan.net/replacing-appdomain-in-dotnet-core/)
* [Getting assembly name](https://stackoverflow.com/questions/4266202/getting-assembly-name)
* [How to invoke a method in interface using reflection](https://stackoverflow.com/questions/9326227/how-to-invoke-a-method-in-interface-using-reflection)
* [Instantiate an object with a runtime-determined type](https://stackoverflow.com/questions/981330/instantiate-an-object-with-a-runtime-determined-type)