Correct Way to Load Assembly, Find Class and Call Run() Method
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?
UPDATE: William Edmondson - see comment
Solution 1:
Use an AppDomain
It is safer and more flexible to load the assembly into its own AppDomain
first.
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 suggest 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.
Solution 2:
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:
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?):
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();
Solution 3:
I'm doing exactly what you're looking for in my rules engine, which uses CS-Script for 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):
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:
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:
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:
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.
Also, use your IRunnable in place of ITestRunner.