Programa de consola de muestra.
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();
    }
}
Quiero construir dinámicamente un ensamblado (.dll) y luego cargar el ensamblado, crear una instancia de una clase y llamar al método Run () de esa clase. ¿Debo intentar transmitir la clase TestRunner a algo? No estoy seguro de cómo los tipos en un ensamblado (código dinámico) sabrían acerca de mis tipos en mi (ensamblaje estático / aplicación de shell). ¿Es mejor usar solo unas pocas líneas de código de reflexión para llamar a Run () solo en un objeto? ¿Cómo debería verse ese código?
ACTUALIZACIÓN: William Edmondson - ver comentario
                    
                        c#
                                .net
                                reflection
                                
                    
                    
                        BuddyJoe
fuente
                
                fuente

exportyimportclases en ensamblajes separados que se derivan de una interfaz conocidaRespuestas:
Utilice un dominio de aplicación
Es más seguro y flexible cargar
AppDomainprimero el conjunto en sí mismo .Entonces, en lugar de la respuesta dada anteriormente :
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();Sugeriría lo siguiente (adaptado de esta respuesta a una pregunta relacionada ):
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();Ahora puede descargar el ensamblaje y tener diferentes configuraciones de seguridad.
Si desea aún más flexibilidad y potencia para la carga y descarga dinámica de ensamblados, debe mirar el marco de complementos administrados (es decir, el
System.AddInespacio de nombres). Para obtener más información, consulte este artículo sobre complementos y extensibilidad en MSDN .fuente
CreateInstanceFromAndUnwraprequiere AssemblyName en lugar de una ruta; ¿te refieresCreateFrom(path, fullname).Unwrap()? También me quemé por elMarshalByRefObjectrequisitoCreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)¿ Quizás ?Si no tiene acceso a la
TestRunnerinformación de tipo en el ensamblado de llamada (parece que no puede), puede llamar al método de esta manera: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);Si tiene acceso al
IRunnabletipo de interfaz, puede convertir su instancia a eso (en lugar delTestRunnertipo, que se implementa en el ensamblaje creado o cargado dinámicamente, ¿verdad?):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();fuente
Estoy haciendo exactamente lo que está buscando en mi motor de reglas, que usa CS-Script para compilar, cargar y ejecutar dinámicamente C #. Debería ser fácilmente traducible a lo que está buscando y le daré un ejemplo. Primero, el código (simplificado):
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; } } }Esto tomará una interfaz de tipo T, compilará un archivo .cs en un ensamblado, instanciará una clase de un tipo dado y alineará esa clase instanciada con la interfaz T. Básicamente, solo debe asegurarse de que la clase instanciada implemente esa interfaz. Utilizo propiedades para configurar y acceder a todo, así:
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; } }Para su ejemplo, desea llamar a Run (), por lo que crearía una interfaz que defina el método Run (), así:
public interface ITestRunner { void Run(); }Luego crea una clase que lo implemente, así:
public class TestRunner : ITestRunner { public void Run() { // implementation goes here } }Cambie el nombre de RulesEngine a algo como TestHarness y establezca sus propiedades:
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; } }Luego, en cualquier lugar que desee llamarlo, puede ejecutar:
ITestRunner testRunner = TestHarnessInterface; if (null != testRunner) { testRunner.Run(); }Probablemente funcionaría muy bien para un sistema de complementos, pero mi código tal como está se limita a cargar y ejecutar un archivo, ya que todas nuestras reglas están en un archivo fuente de C #. Sin embargo, creo que sería bastante fácil modificarlo para pasar el archivo de tipo / fuente para cada uno que quisiera ejecutar. Solo tendría que mover el código del captador a un método que tomara esos dos parámetros.
Además, use su IRunnable en lugar de ITestRunner.
fuente
Deberá utilizar la reflexión para obtener el tipo "TestRunner". Utilice el método 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(); } }fuente
MethodInfodel tipo y llamasInvoke? (Entendí que la pregunta original especificaba que la persona que llama no sabía nada sobre el tipo en cuestión)Cuando construya su conjunto, puede llamar
AssemblyBuilder.SetEntryPointy luego recuperarlo de laAssembly.EntryPointpropiedad para invocarlo.Tenga en cuenta que querrá usar esta firma y tenga en cuenta que no es necesario que tenga un nombre
Main:static void Run(string[] args)fuente