Przykładowy program konsoli.
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();
}
}
Chcę dynamicznie zbudować zestaw (.dll), a następnie załadować zestaw, utworzyć wystąpienie klasy i wywołać metodę Run () tej klasy. Czy powinienem spróbować przesłać na coś klasę TestRunner? Nie jestem pewien, jak typy w jednym zestawie (kod dynamiczny) będą wiedzieć o moich typach w mojej (statycznej aplikacji zestawu / powłoki). Czy lepiej jest po prostu użyć kilku wierszy kodu odbicia, aby wywołać Run () tylko na obiekcie? Jak powinien wyglądać ten kod?
AKTUALIZACJA: William Edmondson - patrz komentarz
c#
.net
reflection
BuddyJoe
źródło
źródło
export
iimport
klasy w oddzielnych zestawach wywodzących się ze znanego interfejsuOdpowiedzi:
Użyj AppDomain
Bezpieczniejsze i bardziej elastyczne jest ładowanie zespołu jako
AppDomain
pierwszego.Więc zamiast odpowiedzi udzielonej wcześniej :
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();
Zasugerowałbym co następuje (dostosowane na podstawie tej odpowiedzi do pokrewnego pytania ):
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();
Teraz możesz zwolnić zestaw i mieć różne ustawienia zabezpieczeń.
Jeśli chcesz jeszcze większej elastyczności i możliwości dynamicznego ładowania i zwalniania zestawów, powinieneś przyjrzeć się strukturze zarządzanych dodatków (tj.
System.AddIn
Przestrzeni nazw). Aby uzyskać więcej informacji, zobacz ten artykuł dotyczący dodatków i rozszerzalności w witrynie MSDN .źródło
CreateInstanceFromAndUnwrap
wymaga raczej AssemblyName niż ścieżki; pan miał na myśliCreateFrom(path, fullname).Unwrap()
? Spalił mnie teżMarshalByRefObject
wymógCreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)
?Jeśli nie masz dostępu do
TestRunner
informacji o typie w zestawie wywołującym (wygląda na to, że nie możesz), możesz wywołać metodę w następujący sposób: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);
Jeśli masz dostęp do
IRunnable
typu interfejsu, możesz rzutować na niego swoje wystąpienie (zamiastTestRunner
typu, który jest zaimplementowany w dynamicznie tworzonym lub ładowanym zestawie, prawda?):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();
źródło
Robię dokładnie to, czego szukasz w moim silniku reguł, który używa CS-Script do dynamicznego kompilowania, ładowania i uruchamiania C #. Powinien być łatwy do przetłumaczenia na to, czego szukasz, a ja podam przykład. Najpierw kod (okrojony):
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; } } }
Spowoduje to pobranie interfejsu typu T, skompilowanie pliku .cs do zestawu, utworzenie instancji klasy danego typu i wyrównanie tej instancji klasy do interfejsu T. Zasadniczo musisz tylko upewnić się, że klasa, której instancja została utworzona, implementuje ten interfejs. Używam właściwości, aby skonfigurować i uzyskać dostęp do wszystkiego, na przykład:
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; } }
Na przykład chcesz wywołać Run (), więc stworzyłbym interfejs, który definiuje metodę Run (), na przykład:
public interface ITestRunner { void Run(); }
Następnie utwórz klasę, która ją implementuje, na przykład:
public class TestRunner : ITestRunner { public void Run() { // implementation goes here } }
Zmień nazwę RulesEngine na coś w rodzaju TestHarness i ustaw właściwości:
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; } }
Następnie, gdziekolwiek chcesz to nazwać, możesz po prostu uruchomić:
ITestRunner testRunner = TestHarnessInterface; if (null != testRunner) { testRunner.Run(); }
Prawdopodobnie działałby świetnie dla systemu wtyczek, ale mój kod w obecnej postaci ogranicza się do ładowania i uruchamiania jednego pliku, ponieważ wszystkie nasze reguły znajdują się w jednym pliku źródłowym C #. Myślę, że byłoby dość łatwo zmodyfikować go, aby po prostu przekazać plik typu / źródłowy dla każdego, który chcesz uruchomić. Wystarczy przenieść kod z metody pobierającej do metody, która przyjmuje te dwa parametry.
Ponadto użyj IRunnable zamiast ITestRunner.
źródło
Będziesz musiał użyć odbicia, aby uzyskać typ „TestRunner”. Użyj metody 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(); } }
źródło
MethodInfo
typ i wezwanieInvoke
? (Zrozumiałem pierwotne pytanie jako określające, że dzwoniący nie wie nic o danym typie.)Podczas budowania zestawu możesz wywołać
AssemblyBuilder.SetEntryPoint
, a następnie pobrać go zAssembly.EntryPoint
właściwości, aby go wywołać.Pamiętaj, że będziesz chciał użyć tego podpisu i pamiętaj, że nie trzeba go nazywać
Main
:static void Run(string[] args)
źródło