Forma correcta de cargar el ensamblaje, buscar clase y llamar al método Run()

Resuelto BuddyJoe asked hace 15 años • 5 respuestas

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. ¿Debería intentar convertir la clase TestRunner en algo? No estoy seguro de cómo los tipos en un ensamblado (código dinámico) conocerían 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

BuddyJoe avatar Jul 16 '09 20:07 BuddyJoe
Aceptado

Utilice un dominio de aplicación

Es más seguro y flexible cargar el conjunto por su cuenta AppDomainprimero.

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 puedes 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 consultar 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 .

cdiggins avatar Jan 06 '2013 17:01 cdiggins

Si no tiene acceso a la TestRunnerinformación de tipo en el ensamblado que realiza la llamada (parece que es posible que no lo tenga), 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 ese (en lugar del TestRunnertipo, que se implementa en el ensamblado 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();
Jeff Sternal avatar Jul 16 '2009 15:07 Jeff Sternal

Estoy haciendo exactamente lo que buscas en mi motor de reglas, que utiliza CS-Script para compilar, cargar y ejecutar C# dinámicamente. Debería ser fácilmente traducible a lo que estás buscando y te 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, creará una instancia de una clase de un tipo determinado y alineará esa clase instanciada con la interfaz T. Básicamente, sólo tienes que asegurarte 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(), como esta:

public interface ITestRunner
{
    void Run();
}

Luego crea una clase que lo implemente, como esta:

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 donde quieras llamarlo, puedes simplemente 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 simplemente pasar el archivo de tipo/fuente para cada uno que quisieras ejecutar. Sólo tendrías que mover el código del captador a un método que tomara esos dos parámetros.

Además, utilice su IRunnable en lugar de ITestRunner.

Chris Doggett avatar Jul 16 '2009 19:07 Chris Doggett