C# – 正确的方式来加载程序集,查找类和调用运行()方法

示例控制台程序。

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

我想要dynamic构build一个程序集(.dll),然后加载程序集,实例化一个类,然后调用该类的Run()方法。 我应该尝试将TestRunner类转换成什么? 不知道一个程序集(dynamic代码)中的types如何知道我的(静态程序集/ shell应用程序)中的types。 只使用几行reflection代码来调用Run()只是一个对象会更好吗? 该代码应该是什么样子?

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

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

我会build议以下(从这个答案改编为一个相关的问题 ):

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

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

如果你想要dynamic加载和卸载程序集的更多的灵活性和能力,你应该看看托pipe加载项框架(即System.AddIn命名空间)。 有关更多信息,请参阅有关MSDN上的加载项和扩展性的文章。

如果你不能访问调用程序集中的TestRunnertypes信息(听起来你可能不会),你可以调用这个方法:

 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接口types,那么可以将实例IRunnable为(而不是在dynamic创build或加载的程序集中实现的TestRunnertypes,对吗?):

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

在我的规则引擎中,我正在使用CS-Script来dynamic编译,加载和运行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; } } } 

这将采用typesT的接口,将.cs文件编译成程序集,实例化给定types的类,并将该实例化类与T接口alignment。 基本上,你只需要确保实例化的类实现了这个接口。 我使用属性来设置和访问一切,如下所示:

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

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

 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#源文件中。 不过,我认为将其修改为只传递每个要运行的types/源文件是相当容易的。 你只需要把代码从getter移到一个带有这两个参数的方法。

另外,使用IRunnable替代ITestRunner。

您将需要使用reflection来获取“TestRunner”types。 使用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(); } } 

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

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

 static void Run(string[] args)