Je suis en train de faire exactement ce que vous cherchez dans mon moteur de règles, qui utilise CS-Script dynamique de la compilation, le chargement et l'exécution de C#. Il devrait être facilement traduisibles en ce que vous cherchez, et je vais vous donner un exemple. D'abord, le 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;
}
}
}
Ceci prendra une interface de type T, de compiler une .cs fichier dans une assemblée, d'instancier une classe d'un type donné, et de les aligner que instancié de la classe à l'interface. Fondamentalement, vous avez juste à vous assurer que le instancié de la classe implémentant cette interface. J'utilise les propriétés de configuration et d'accès à tout, comme ceci:
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; }
}
Pour votre exemple, vous voulez appeler Run(), donc je voudrais faire une interface qui définit la méthode Run (), comme ceci:
public interface ITestRunner
{
void Run();
}
Ensuite, faire une classe qui l'implémente, comme ceci:
public class TestRunner : ITestRunner
{
public void Run()
{
// implementation goes here
}
}
Changer le nom de RulesEngine à quelque chose comme TestHarness, et l'ensemble de vos propriétés:
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; }
}
Ensuite, n'importe où vous voulez l'appeler, il vous suffit d'exécuter:
ITestRunner testRunner = TestHarnessInterface;
if (null != testRunner)
{
testRunner.Run();
}
Il serait sans doute grand travail pour un système de plugin, mais mon code est est limitée à chargement et l'exécution d'un fichier, étant donné que toutes nos règles sont en C# fichier source. Je pense que ce serait assez facile à modifier qu'il vient de passer dans le type de/le fichier source pour chaque vous envie de courir, de si. Vous avez juste à déplacer le code de la lecture dans une méthode qui a pris ces deux paramètres.
Aussi, utilisez votre IRunnable en place de ITestRunner.