c# - pila - Forma correcta de cargar el ensamblado, el método Find Class y Call Run()
como cargar mi celular sin electricidad (5)
Use un AppDomain
Es más seguro y más flexible cargar el ensamblado en su propio AppDomain
primero.
Entonces, en lugar de la respuesta dada previamente :
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 conjuntos, debe consultar el Marco de complementos gestionados (es decir, el espacio de nombres System.AddIn
). Para obtener más información, consulte este artículo sobre complementos y extensibilidad en MSDN .
Ejemplo de programa de consola.
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 instancia 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 conjunto (código dinámico) sabrían sobre mis tipos en mi (montaje estático / aplicación de shell). ¿Es mejor simplemente usar unas líneas de código de reflexión para llamar a Run () solo en un objeto? ¿Cómo debería ser ese código?
ACTUALIZACIÓN: William Edmondson - ver comentario
Cuando construya su ensamblado, puede llamar a AssemblyBuilder.SetEntryPoint
y luego recuperarlo desde la propiedad Assembly.EntryPoint
para invocarlo.
Tenga en cuenta que querrá usar esta firma, y tenga en cuenta que no tiene que llamarse Main
:
static void Run(string[] args)
Deberá usar el reflejo para obtener el tipo "TestRunner". Use 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();
}
}
Estoy haciendo exactamente lo que está buscando en mi motor de reglas, que utiliza CS-Script para compilar, cargar y ejecutar C # dinámicamente. Debería traducirse fácilmente a lo que estás buscando, y daré un ejemplo. Primero, el código (despojado):
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 ensamblaje, instanciará una clase de un tipo determinado y alineará esa clase instanciada a la interfaz T. Básicamente, solo tienes que asegurarte de que la clase instanciada implemente esa interfaz. Yo uso 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, quiere llamar a Run (), entonces crearía una interfaz que define 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
}
}
Cambia el nombre de RulesEngine a algo como TestHarness, y configura tus 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 simplemente ejecutar:
ITestRunner testRunner = TestHarnessInterface;
if (null != testRunner)
{
testRunner.Run();
}
Probablemente funcione bien para un sistema de complementos, pero mi código tal como está está limitado a cargar y ejecutar un archivo, ya que todas nuestras reglas están en un solo archivo fuente C #. Sin embargo, creo que sería bastante fácil modificarlo para pasar el archivo de tipo / fuente para cada uno que quisiera ejecutar. Tendría que mover el código del getter a un método que tomara esos dos parámetros.
Además, use su IRunnable en lugar de ITestRunner.
Si no tiene acceso a la información del tipo TestRunner
en el conjunto de llamadas (parece que no), 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 tipo de interfaz IRunnable
, puede convertir su instancia a esa (en lugar del tipo TestRunner
, que se implementa en el ensamblaje creado o cargado dinámicamente, ¿no?):
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();