注目キーワード
  1. プログラミング
  2. C#

【C#】動的に読み込んだDLLに実装されているクラスを使用する方法

C#

はじめに

こうちゃ
こんにちは、こうちゃです。

今回は、C#において動的に読み込んだDLLに実装されているクラスの使用方法 について説明していきます。
今回の内容は下記記事の続きですので、まだ読んでいない方は下記記事に目を通してから本記事を読むことをお勧めします。

関連記事

はじめに こうちゃ こんにちは、こうちゃです。 今回は、C#におけるDLLの動的読み込み方法について説明していきます。 C#でDLLを動的に読み込む方法 読み込んだDLLに実装されているクラスのイン[…]

  • C#で動的に読み込んだDLLに実装されているクラスのインスタンスを作成する方法
  • クラスに実装されているメソッドを使用する方法
  •  C#においてDLLを動的に読み込ませ、DLLに実装されているクラスを使用したい方
こうちゃ
それでは、やっていきましょう!

フォルダ構成

今回のフォルダ構成は下図のようになります。

ソリューションディレクトリ
│  
├─プロジェクト (Car)
│      Car.cs
│      Car.csproj
│
│
├─プロジェクト(Common)
│  │  Common.csproj
│  │          
│  └─Interfaces
│        IObject.cs
│      
│          
├─プロジェクト (Human)
│      Human.cs
│      Human.csproj
│          
└─プロジェクト(Main)
    │  Main.csproj
    │  Main.cs
    │  
    └─bin
        └─x64
            └─Release
                └─net8.0
                    │  Common.dll
                    │  Main.dll
                    │  Main.exe
                    │  
                    └─DLLs
                            Car.dll
                            Common.dll
                            Human.dll

前回と違う点としては、「プロジェクト(Common)」が追加された点です。
このプロジェクトではインターフェースを定義しており、他のすべてのプロジェクトから参照されます
このプロジェクトが存在することで、動的に読み込んだDLLに実装されているクラスを使用することが可能になります。

ソースコード

実際にソースコードを見てみましょう。
まず、今回追加された「IObject.cs」から見ていきましょう。

namespace Common.Interfaces
{
    public interface IObject
    {
        string GetObjectName();
    }
}

中身としては非常に簡単で、「GetObjectName」というメソッドを実装する インターフェース「IObject」が定義されています。
今回は、この「IObject」というインターフェースが非常に重要になります。

続いて、「Car.cs」を見てみましょう。

using Common.Interfaces;

namespace Car
{
    public class Car : IObject
    {
        public string GetObjectName()
        {
            return "車";
        }
    }
}

前回は中身が空のクラスが定義されているだけでしたが、今回は「IObject」を継承しています。
そのため、「GetObjectName」メソッドが実装されています。
メソッドの中身は、簡単に「車」という文字列を返すのみとしています。

次に「Human.cs」を見ていきましょう。

using Common.Interfaces;

namespace Human
{
    public class Human : IObject
    {
        public string GetObjectName()
        {
            return "人間";
        }
    }
}

こちらも前回は空のクラスが定義されているだけでしたが、「Car」クラス同様、「IObject」を継承しています。
そのため、「GetObjectName」メソッドを実装しており、メソッドの中身としては「人間」という文字列を返すようになっています。

最後に、「Main.cs」を見ていきます。
前回同様、まずはDLLの読み込みとインスタンス作成を行う「LoadDll_CreateInstance」を見ていきましょう。

/// <summary>
/// DLL読み込み & インスタンス作成
/// </summary>
/// <param name="dllDirPath">読み込みたいDLLが配置されているフォルダへのパス</param>
/// <param name="objList">作成したインスタンスリスト</param>
static void LoadDll_CreateInstance(string dllDirPath, List<IObject> objList)
{
    if (Directory.Exists(dllDirPath))
    {
        // dllDirPath内に存在する".dll"ファイルをピックアップしリスト化する
        List<string> dlls = Directory.GetFiles(dllDirPath, "*.dll").ToList();

        foreach (string dllName in dlls)
        {
            // DLLを読み込む
            Assembly assembly = Assembly.LoadFrom(dllName);

            // DLLに実装されているすべての型を取得する
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                // 非クラス型、非パブリック型、抽象クラスはスキップ
                if (!type.IsClass || !type.IsPublic || type.IsAbstract)
                {
                    continue;
                }

                // インスタンスを作成する
                IObject? obj = Activator.CreateInstance(type) as IObject;
                if (obj != null)
                {
                    // 作成したインスタンスが null でなければ、引数のインスタンスリストに格納する
                    objList.Add(obj);
                }
            }
        }
    }
}

基本的に、前回とやっていることは大きく変わりませんが、変更されている箇所が2つあります。
1つ目は、メソッドの第二引数。
2つ目は、インスタンス作成部分です。
まず1つ目の、第二引数について。
下記、上のコードが前回、下のコードが今回です。

static void LoadDll_CreateInstance(string dllDirPath, List<object> objList)
static void LoadDll_CreateInstance(string dllDirPath, List<IObject> objList)

Listで指定している型が、前回は「object」でしたが、今回は「IObject」となっています。
つまり、IObject型のインスタンスでないと「objList」には格納できなくなっています。

それでは2つ目、インスタンス作成部分について見ていきましょう。
下記、上のコードが前回、下のコードが今回です。

object? obj = Activator.CreateInstance(type);
IObject? obj = Activator.CreateInstance(type) as IObject;

前回は、「Activator.CreateInstance」で生成したobject型のインスタンスをそのまま取得していました。
今回は、「Activator.CreateInstance」で生成したobject型のインスタンスが「IObject」型かどうか(「IObject」を継承しているか)を見ています。
もし生成されたインスタンスが「IObject」型でなかった場合、「obj」はnullになります。
このようにして、今回は「IObject」型のインスタンスのみを取得するようにしています。

次に、この「LoadDll_CreateInstance」メソッドを呼び出す「Main」メソッドを見ていきます。

static void Main(string[] args)
{
    // 読み込みたいDLLが配置されているフォルダへのパス
    string dllDirPath = $"{AppDomain.CurrentDomain.BaseDirectory}DLLs";
    List<IObject> objList = new List<IObject>();
    LoadDll_CreateInstance(dllDirPath, objList);

    foreach (IObject obj in objList)
    {
        Console.WriteLine($"GetType = {obj.GetType().ToString()}");
        Console.WriteLine($"GetObjectName = {obj.GetObjectName()}");
    }
}

前回との違いとしては、「objList」が「IObject」型であることです。
結果出力部分としては、前回同様、「objList」の各インスタンスに対して「GetType」の結果を出力しています。
また今回は、「IObject」が「GetObjectName」メソッドを実装しているため、各インスタンスから「GetObjectName」メソッドを呼び出してその結果の出力を追加しました。

「Main.cs」全体のコードは以下のようになります。

using System.Reflection;
using Common.Interfaces;

namespace Main
{
    class Program
    {
        static void Main(string[] args)
        {
            // 読み込みたいDLLが配置されているフォルダへのパス
            string dllDirPath = $"{AppDomain.CurrentDomain.BaseDirectory}DLLs";
            List<IObject> objList = new List<IObject>();
            LoadDll_CreateInstance(dllDirPath, objList);

            foreach (IObject obj in objList)
            {
                Console.WriteLine($"GetType = {obj.GetType().ToString()}");
                Console.WriteLine($"GetObjectName = {obj.GetObjectName()}");
            }
        }

        /// <summary>
        /// DLL読み込み & インスタンス作成
        /// </summary>
        /// <param name="dllDirPath">読み込みたいDLLが配置されているフォルダへのパス</param>
        /// <param name="objList">作成したインスタンスリスト</param>
        static void LoadDll_CreateInstance(string dllDirPath, List<IObject> objList)
        {
            if (Directory.Exists(dllDirPath))
            {
                // dllDirPath内に存在する".dll"ファイルをピックアップしリスト化する
                List<string> dlls = Directory.GetFiles(dllDirPath, "*.dll").ToList();

                foreach (string dllName in dlls)
                {
                    // DLLを読み込む
                    Assembly assembly = Assembly.LoadFrom(dllName);

                    // DLLに実装されているすべての型を取得する
                    Type[] types = assembly.GetTypes();
                    foreach (Type type in types)
                    {
                        // 非クラス型、非パブリック型、抽象クラスはスキップ
                        if (!type.IsClass || !type.IsPublic || type.IsAbstract)
                        {
                            continue;
                        }

                        // インスタンスを作成する
                        IObject? obj = Activator.CreateInstance(type) as IObject;
                        if (obj != null)
                        {
                            // 作成したインスタンスが null でなければ、引数のインスタンスリストに格納する
                            objList.Add(obj);
                        }
                    }
                }
            }
        }
    }
}

実施結果

それでは、上記のコードを実行してみましょう。
実行すると、コンソールに以下が表示されます。

GetType = Car.Car
GetObjectName = 車
GetType = Human.Human
GetObjectName = 人間

「GetType」の方は、前回同様、「名前空間.クラス名」が表示されています。
「GetObjectName」の方は、各クラスの「GetObjectName」メソッドで実装した文字列が表示されています。
「Car.dll」「Human.dll」を動的に読み込みインスタンス化を行い、それぞれ実装されているメソッドを呼び出すことができました。

さいごに

今回は、動的に読み込んだDLLに実装されているクラスのメソッドを呼び出す方法について説明しました。
インターフェースを使用することで、DLLに実装されているクラスで所有しているメソッドを認識できるようになるため、呼び出すことが可能になります。
この方法をさらに突き詰めていくと、プロジェクト参照を行うことなく、他プロジェクトに実装されているクラスを使用するなんてこともできるようになってきます。
そのやり方については、また別の記事で紹介したいと思います。
今回はここまで♪

こうちゃ
それではみなさま、お疲れ様でした!
楽しいプログラミングライフを!
最新情報をチェックしよう!