正确的方式加载程序集,查找类并调用Run()方法


81

示例控制台程序。

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类转换为某些东西吗?不知道一个程序集(动态代码)中的类型如何知道我的(静态程序集/ shell应用程序)中的类型。仅使用几行反射代码在一个对象上调用Run()更好吗?该代码应该是什么样的?

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


从未来讲...您是否曾与MEF合作?让您exportimport来自已知接口的单独程序集中的类
RJB

Answers:


77

使用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上有关加载项和可扩展性的文章。


1
如果TypeIWantToLoad是字符串怎么办?您是否可以替代上一个答案的asm.GetType(“ type string”)?
paz

2
我认为CreateInstanceFromAndUnwrap需要AssemblyName而不是路径;你的意思是CreateFrom(path, fullname).Unwrap()?我也被MarshalByRefObject要求
drzaus

1
也许CreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)吧?
淡出

1
大家好,我相信您将CreateInstanceAndUnwrap与CreateInstanceFromAndUnwrap混淆了。
cdiggins's

48

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

+1使用type.invokeMember行工作。我应该使用该方法还是继续尝试对接口进行操作?我什至不必担心将其放入动态构建的代码中。
BuddyJoe

嗯,第二段代码对您不起作用吗?您的调用程序集是否可以访问IRunnable类型?
杰夫·斯坦恩

第二块确实起作用。调用程序集并不真正了解IRunnable。所以我想我会坚持第二种方法。稍作跟进。当我重新生成代码,然后重做dyn.dll时,由于使用中,我似乎无法替换它。像Assembly.UnloadType之类的东西还是让我替换.dll的东西?还是我应该“在内存中”做?有什么想法吗?谢谢
BuddyJoe

如果这是最佳解决方案,请猜我不知道执行“内存中”操作的正确方法。
BuddyJoe

我不记得具体细节了(我暂时要离开计算机了),但是我相信每个AppDomain只能向一个Assembly加载一次Assembly-因此,您将不得不为每个Assembly实例创建新的AppDomain(并将程序集加载到其中),否则您必须重新启动应用程序才能编译程序集的新版本。
杰夫·斯坦恩

12

我正在完全按照您在我的规则引擎中寻找的内容进行操作,该引擎使用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();
}

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

另外,使用IRunnable代替ITestRunner。


什么是@Interface?这里很酷的想法。需要充分消化这一点。+1
BuddyJoe

非常有趣,我没有意识到C#解析器必须通过@来查找一个字符,以查看它是否是变量名或@“”字符串的一部分。
BuddyJoe

谢谢。当变量名是关键字时,在变量名前使用@。您不能将变量命名为“类”,“接口”,“新”等。但是如果在@前面加上前缀,则可以。在我的情况下,大写字母“ I”可能无关紧要,但在将其转换为自动属性之前,它最初是一个带有吸气剂和设置器的内部变量。
克里斯·多吉特

那就对了。我忘了@东西。您将如何处理我不得不向Jeff Sternal提出的有关“内存中事物”的问题?我想我现在的大问题是我可以构建动态.dll并加载它,但只能执行一次。不知道如何“卸载”装配。是否可以创建另一个AppDomain在该空间中加载程序集,使用它,然后删除此第二个AppDomain。冲洗。重复。?
BuddyJoe

1
除非您使用第二个AppDomain,否则无法卸载程序集。我不确定CS-Script在内部如何执行此操作,但是我除去的规则引擎部分是FileSystemWatcher,该FileSystemWatcher每当文件更改时都会自动再次运行LoadRules()。我们编辑规则,将其推出给用户,用户将其客户端覆盖该文件,FileSystemWatcher会注意到更改,并通过在temp目录中写入另一个文件来重新编译并重新加载DLL。当客户端启动时,它将在第一次动态编译之前清除该目录,因此我们没有大量的剩余物。
克里斯·多吉特

6

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

这是不是缺少一个MethodInfo从类型和调用中获取适当信息的步骤Invoke?(我理解最初的问题是指定呼叫者对所讨论的类型一无所知。)
Jeff Sternal

您缺少一件事。您必须强制转换obj以键入TestRunner。var obj =(TestRunner)Activator.CreateInstance(type);
BFree

听起来Tyndall实际上是在较早的步骤中构建了该dll。此实现假定他知道方法Run()已经存在,并且知道它没有参数。如果确实不知道这些,那么他将需要做一些更深的思考
William Edmondson

嗯。TestRunner是我的动态编写代码中的一个类。因此,您的示例中的此静态代码无法解析TestRunner。它不知道它是什么。
BuddyJoe

@WilliamEdmondson,如何在代码中使用“(TestRunner)”,因为此处未引用?
Antoops

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.