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
Correct Way to Load Assembly, Find Class and Call Run() Method
提问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 AppDomain
first.
首先将组件加载到自己的组件中更安全、更灵活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.AddIn
namespace). 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 TestRunner
type 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 IRunnable
interface type, you can cast your instance to that (rather than the TestRunner
type, 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.EntryPoint
property 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。