C# 加载程序集、查找类和调用 Run() 方法的正确方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1137781/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 09:07:20  来源:igfitidea点击:

Correct Way to Load Assembly, Find Class and Call Run() Method

c#.netreflection

提问by BuddyJoe

Sample console program.

示例控制台程序。

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();

    }
}

I want to dynamically build an assembly (.dll), and then load the assembly, instantiate a class, and call the Run() method of that class. Should I try casting the TestRunner class to something? Not sure how the types in one assembly (dynamic code) would know about my types in my (static assembly / shell app). Is it better to just use a few lines of reflection code to call Run() on just an object? What should that code look like?

我想动态构建一个程序集(.dll),然后加载程序集,实例化一个类,并调用该类的 Run() 方法。我应该尝试将 TestRunner 类转换为某些东西吗?不确定一个程序集中的类型(动态代码)如何知道我的(静态程序集/外壳应用程序)中的类型。仅使用几行反射代码在一个对象上调用 Run() 是否更好?这段代码应该是什么样的?

UPDATE: William Edmondson - see comment

更新:威廉埃德蒙森 - 见评论

采纳答案by cdiggins

Use an AppDomain

使用 AppDomain

It is safer and more flexible to load the assembly into its own AppDomainfirst.

首先将组件加载到自己的组件中更安全、更灵活AppDomain

So instead of the answer given previously:

因此,而不是之前给出的答案

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();

I would suggested the following (adapted from this answer to a related question):

我会提出以下建议(改编自对相关问题的回答):

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();

Now you can unload the assembly and have different security settings.

现在您可以卸载程序集并拥有不同的安全设置。

If you want even more flexibility and power for dynamic loading and unloading of assemblies you should look at the Managed Add-ins Framework (i.e. the System.AddInnamespace). For more information see this article on Add-ins and Extensibility on MSDN.

如果您想要动态加载和卸载程序集的更多灵活性和功能,您应该查看托管加载项框架(即System.AddIn命名空间)。有关更多信息,请参阅MSDN 上有关插件和可扩展性的这篇文章。

回答by William Edmondson

You will need to use reflection to get the type "TestRunner". Use the Assembly.GetType method.

您将需要使用反射来获取“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();
    }
}

回答by Jeff Sternal

If you do not have access to the TestRunnertype information in the calling assembly (it sounds like you may not), you can call the method like this:

如果您无权访问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);

If you have access to the IRunnableinterface type, you can cast your instance to that (rather than the TestRunnertype, which is implemented in the dynamically created or loaded assembly, right?):

如果您有权访问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();

回答by Sam Harwell

When you build your assembly, you can call AssemblyBuilder.SetEntryPoint, and then get it back from the Assembly.EntryPointproperty to invoke it.

构建程序集时,您可以调用AssemblyBuilder.SetEntryPoint,然后从Assembly.EntryPoint属性中取回它以调用它。

Keep in mind you'll want to use this signature, and note that it doesn't have to be named Main:

请记住,您将要使用此签名,并注意它不必被命名Main

static void Run(string[] args)

回答by Chris Doggett

I'm doing exactly what you're looking for in my rules engine, which uses CS-Scriptfor dynamically compiling, loading, and running C#. It should be easily translatable into what you're looking for, and I'll give an example. First, the code (stripped-down):

我正在做的正是你在我的规则引擎中寻找的东西,它使用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;
        }
    }
}

This will take an interface of type T, compile a .cs file into an assembly, instantiate a class of a given type, and align that instantiated class to the T interface. Basically, you just have to make sure the instantiated class implements that interface. I use properties to setup and access everything, like so:

这将采用 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; }
}

For your example, you want to call Run(), so I'd make an interface that defines the Run() method, like this:

对于您的示例,您想调用 Run(),因此我将创建一个定义 Run() 方法的接口,如下所示:

public interface ITestRunner
{
    void Run();
}

Then make a class that implements it, like this:

然后创建一个实现它的类,如下所示:

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

Change the name of RulesEngine to something like TestHarness, and set your properties:

将 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; }
}

Then, anywhere you want to call it, you can just run:

然后,在任何你想调用它的地方,你可以运行:

ITestRunner testRunner = TestHarnessInterface;

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

It would probably work great for a plugin system, but my code as-is is limited to loading and running one file, since all of our rules are in one C# source file. I would think it'd be pretty easy to modify it to just pass in the type/source file for each one you wanted to run, though. You'd just have to move the code from the getter into a method that took those two parameters.

它可能适用于插件系统,但我的代码仅限于加载和运行一个文件,因为我们所有的规则都在一个 C# 源文件中。不过,我认为修改它只需为您想要运行的每个文件传递类型/源文件,这很容易。您只需将代码从 getter 移动到采用这两个参数的方法中。

Also, use your IRunnable in place of ITestRunner.

此外,使用您的 IRunnable 代替 ITestRunner。