Ich mache genau das, wonach Sie in meiner Regel-Engine suchen, die verwendet CS-Script zum dynamischen Kompilieren, Laden und Ausführen von C # verwendet. Es sollte leicht in das zu übersetzen sein, wonach Sie suchen, und ich werde ein Beispiel geben. Zuerst der Code (abgespeckte):
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;
}
}
}
Dies nimmt eine Schnittstelle vom Typ T, kompiliert eine CS-Datei in eine Assembly, instanziiert eine Klasse eines bestimmten Typs und richtet diese instanziierte Klasse an der T-Schnittstelle aus. Grundsätzlich müssen Sie nur sicherstellen, dass die instanziierte Klasse diese Schnittstelle implementiert. Ich benutze Eigenschaften, um alles einzurichten und darauf zuzugreifen, wie folgt:
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; }
}
In Ihrem Beispiel möchten Sie Run () aufrufen, daher würde ich eine Schnittstelle erstellen, die die Run () -Methode wie folgt definiert:
public interface ITestRunner
{
void Run();
}
Erstellen Sie dann eine Klasse, die sie wie folgt implementiert:
public class TestRunner : ITestRunner
{
public void Run()
{
// implementation goes here
}
}
Ändern Sie den Namen von RulesEngine in TestHarness und legen Sie Ihre Eigenschaften fest:
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; }
}
Dann können Sie überall dort, wo Sie es aufrufen möchten, einfach Folgendes ausführen:
ITestRunner testRunner = TestHarnessInterface;
if (null != testRunner)
{
testRunner.Run();
}
Es würde wahrscheinlich gut für ein Plugin-System funktionieren, aber mein Code ist so wie er ist auf das Laden und Ausführen einer Datei beschränkt, da sich alle unsere Regeln in einer C # -Quelldatei befinden. Ich würde denken, dass es ziemlich einfach wäre, es so zu ändern, dass nur die Typ- / Quelldatei für jede Datei übergeben wird, die Sie ausführen möchten. Sie müssten nur den Code aus dem Getter in eine Methode verschieben, die diese beiden Parameter verwendet.
Verwenden Sie Ihr IRunnable auch anstelle von ITestRunner.
export
undimport
Klassen in separaten Baugruppen von einer bekannten Schnittstelle ableiten