web-dev-qa-db-ja.com

アセンブリのロード、クラスの検索、およびRun()メソッドの呼び出しの正しい方法

サンプルコンソールプログラム。

class Program
{
    static void Main(string[] args)
    {
        // ... code to build dll ... not written yet ...
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        // don't know what or how to cast here
        // looking for a better way to do next 3 lines
        IRunnable r = Assembly.CreateInstance("TestRunner");
        if (r == null) throw new Exception("broke");
        r.Run();

    }
}

アセンブリ(.dll)を動的に構築してから、アセンブリをロードし、クラスをインスタンス化し、そのクラスのRun()メソッドを呼び出します。 TestRunnerクラスを何かにキャストしてみてください。 1つのアセンブリ(動的コード)の型が、私の(静的アセンブリ/シェルアプリ)の型についてどのように知るかわかりません。オブジェクトだけでRun()を呼び出すために、数行のリフレクションコードを使用する方が良いでしょうか?そのコードはどのように見えるべきですか?

更新:ウィリアム・エドモンドソン-コメントを参照

78
BuddyJoe

AppDomainを使用する

最初にアセンブリを独自の AppDomain にロードする方が安全で柔軟性があります。

以前に与えられた答え の代わりに:

var asm = Assembly.LoadFile(@"C:\myDll.dll");
var type = asm.GetType("TestRunner");
var runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

私は以下を提案します( 関連する質問へのこの回答 から適応):

var domain = AppDomain.CreateDomain("NewDomainName");
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(@"C:\myDll.dll", t.Name) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();

これで、アセンブリをアンロードして、異なるセキュリティ設定を使用できます。

アセンブリの動的なロードとアンロードの柔軟性とパワーがさらに必要な場合は、マネージアドインフレームワーク(つまり、System.AddIn名前空間)を確認する必要があります。詳細については、 MSDNのアドインと拡張性 に関するこの記事を参照してください。

70
cdiggins

呼び出し元アセンブリのTestRunner型情報にアクセスできない場合(そうではないように聞こえます)、次のようにメソッドを呼び出すことができます。

Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
Type     type     = Assembly.GetType("TestRunner");
var      obj      = Activator.CreateInstance(type);

// Alternately you could get the MethodInfo for the TestRunner.Run method
type.InvokeMember("Run", 
                  BindingFlags.Default | BindingFlags.InvokeMethod, 
                  null,
                  obj,
                  null);

IRunnableインターフェイスタイプにアクセスできる場合は、動的に作成またはロードされたアセンブリに実装されているTestRunnerタイプではなく、インスタンスをそのインスタンスにキャストできます。

  Assembly assembly  = Assembly.LoadFile(@"C:\dyn.dll");
  Type     type      = Assembly.GetType("TestRunner");
  IRunnable runnable = Activator.CreateInstance(type) as IRunnable;
  if (runnable == null) throw new Exception("broke");
  runnable.Run();
46
Jeff Sternal

私はルールエンジンであなたが探しているものを正確にやっています。 CS-Script を使用して、C#を動的にコンパイル、ロード、実行します。探しているものに簡単に翻訳できるはずです。例を挙げましょう。まず、コード(削除済み):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScriptLibrary;

namespace RulesEngine
{
    /// <summary>
    /// Make sure <typeparamref name="T"/> is an interface, not just any type of class.
    /// 
    /// Should be enforced by the compiler, but just in case it's not, here's your warning.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RulesEngine<T> where T : class
    {
        public RulesEngine(string rulesScriptFileName, string classToInstantiate)
            : this()
        {
            if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName");
            if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate");

            if (!File.Exists(rulesScriptFileName))
            {
                throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName);
            }

            RulesScriptFileName = rulesScriptFileName;
            ClassToInstantiate = classToInstantiate;

            LoadRules();
        }

        public T @Interface;

        public string RulesScriptFileName { get; private set; }
        public string ClassToInstantiate { get; private set; }
        public DateTime RulesLastModified { get; private set; }

        private RulesEngine()
        {
            @Interface = null;
        }

        private void LoadRules()
        {
            if (!File.Exists(RulesScriptFileName))
            {
                throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName);
            }

            FileInfo file = new FileInfo(RulesScriptFileName);

            DateTime lastModified = file.LastWriteTime;

            if (lastModified == RulesLastModified)
            {
                // No need to load the same rules twice.
                return;
            }

            string rulesScript = File.ReadAllText(RulesScriptFileName);

            Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true);

            @Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface<T>();

            RulesLastModified = lastModified;
        }
    }
}

これは、T型のインターフェイスを受け取り、.csファイルをアセンブリにコンパイルし、特定の型のクラスをインスタンス化し、そのインスタンス化されたクラスをTインターフェイスに合わせます。基本的に、インスタンス化されたクラスがそのインターフェースを実装することを確認するだけです。次のように、プロパティを使用してすべてをセットアップし、すべてにアクセスします。

private RulesEngine<IRulesEngine> rulesEngine;

public RulesEngine<IRulesEngine> RulesEngine
{
    get
    {
        if (null == rulesEngine)
        {
            string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");

            rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName);
        }

        return rulesEngine;
    }
}

public IRulesEngine RulesEngineInterface
{
    get { return RulesEngine.Interface; }
}

あなたの例では、Run()を呼び出したいので、次のようにRun()メソッドを定義するインターフェースを作成します。

public interface ITestRunner
{
    void Run();
}

次に、次のように、それを実装するクラスを作成します。

public class TestRunner : ITestRunner
{
    public void Run()
    {
        // implementation goes here
    }
}

RulesEngineの名前をTestHarnessなどに変更し、プロパティを設定します。

private TestHarness<ITestRunner> testHarness;

public TestHarness<ITestRunner> TestHarness
{
    get
    {
        if (null == testHarness)
        {
            string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");

            testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName);
        }

        return testHarness;
    }
}

public ITestRunner TestHarnessInterface
{
    get { return TestHarness.Interface; }
}

次に、呼び出したい場所ならどこでも実行できます:

ITestRunner testRunner = TestHarnessInterface;

if (null != testRunner)
{
    testRunner.Run();
}

プラグインシステムではおそらくうまく機能しますが、ルールはすべて1つのC#ソースファイルに含まれているため、私のコードは1つのファイルの読み込みと実行に制限されています。ただし、実行したいそれぞれのタイプ/ソースファイルを渡すように変更するのは非常に簡単だと思います。コードをゲッターからこれらの2つのパラメーターを取るメソッドに移動するだけです。

また、ITestRunnerの代わりにIRunnableを使用します。

11
Chris Doggett

タイプ「TestRunner」を取得するには、リフレクションを使用する必要があります。 Assembly.GetTypeメソッドを使用します。

class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
        Type type = Assembly.GetType("TestRunner");
        var obj = (TestRunner)Activator.CreateInstance(type);
        obj.Run();
    }
}
5

アセンブリをビルドするときに、 AssemblyBuilder.SetEntryPoint を呼び出してから、 Assembly.EntryPoint プロパティから呼び出して呼び出すことができます。

この署名を使用することを忘れないでください。Mainという名前を付ける必要はありません。

static void Run(string[] args)
2
Sam Harwell