Cómo crear una clase dinámicamente y ejecutarlaSábado, 14 Nov, 2009 @ 19:13 | Por Gustavo Cantero (The Wolf) | .NET Framework |
Revisando los newsgroups de Microsoft encontré una persona que preguntaba cómo hacer para “ejecutar un string”, o sea, escribir una fórmula matemática en una cadena de texto y luego obtener el resultado de la misma. En ese momento recordé que hace unos años tuve que hacer esto mismo utilizando .NET Framework 1.1, fue entonces cuando me decidí a buscar aquel código y escribir este artículo.
Para hacer esto necesitamos crear una clase, compilarla en memoria, luego instanciarla y por último ejecutar el método que devuelva el resultado en cuestión.
La clase la debemos crear en un string incluyendo los using necesarios, el namespace a utilizar y el método a ejecutar, el cual va a resolver la fórmula. Al compilador debemos pasarle varios parámetros a través de la clase CompilerParameters, donde vamos a indicarle que debe generar el ensamblado en memoria, que no debe generar un ejecutable y que no incluya (o si, depende de la necesidad) la información para debug.
CompilerParameters Parametros = new CompilerParameters()
{
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = false
};
Luego debemos crear el compilador con el método estático CreateProvider de la clase CodeDomProvider, al cual debemos pasarle el lenguaje que queremos utilizar, en nuestro ejemplo “CSharp”. Hay que tener en cuenta que el nombre del lenguaje es “case sensitive”, por lo que hay que tener cuidado de escribirlo con las mayúsculas y minúsculas correspondientes.
CodeDomProvider objCompiler = CodeDomProvider.CreateProvider("CSharp");
Una vez creado el compilador debemos pasarle la clase y los parámetros creados anteriormente para que genere el ensamblado necesario en memoria:
CompilerResults objResultados = objCompiler.CompileAssemblyFromSource(objParametros, strClase);
Por último debemos crear una instancia de la clase y llamar al método que creamos y que va a calcular la fórmula:
object objClase = objResultados.CompiledAssembly.CreateInstance("MiNamespace.MiClase", false, BindingFlags.CreateInstance, null, null, null, null);
return objClase.GetType().InvokeMember("MiMetodo", BindingFlags.InvokeMethod, null, objClase, null);
El método CreateInstance posee varios parámetros para globalización, parámetros para pasarle al método, etc., pero para nuestro ejemplo, al no necesitarlos, los vamos a establecer en null.
Una vez explicado lo que necesitamos hacer, les paso el código del método a ejecutar para que resuelva fórmulas o cualquier línea de C# (por ejemplo, búsquedas de cadenas de texto, etc.) y devuelva el valor:
namespace WebApplication1
{
using System.CodeDom.Compiler;
using System.Reflection;
public static class Formula
{
/// <summary>
/// Resuelve el valor de una fórmula
/// </summary>
/// <param name="Formula">Fórmula a resolver</param>
/// <returns>Resultado</returns>
/// <example>double Resultado = Formula.Resolver("2 * 8 + 3");</example>
public static object Resolver(string Formula)
{
//Parámetros del compilador
CompilerParameters objParametros = new CompilerParameters()
{
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = false
};
//Clase
string strClase =
"using System;" +
"namespace Scientia {" +
"public class Formula {" +
"public object Ejecutar() {" +
"return " + Formula +
";}}}";
//Compilo todo y ejecuto el método
CodeDomProvider objCompiler = CodeDomProvider.CreateProvider("CSharp");
//En .NET 1.1 usaba esta linea:
//ICodeCompiler ICC = (new CSharpCodeProvider()).CreateCompiler();
CompilerResults objResultados = objCompiler.CompileAssemblyFromSource(objParametros, strClase);
object objClase = objResultados.CompiledAssembly.CreateInstance("Scientia.Formula",
false, BindingFlags.CreateInstance, null, null, null, null);
return objClase.GetType().InvokeMember("Ejecutar", BindingFlags.InvokeMethod, null, objClase, null);
}
}
}
Como ejemplo podemos crear una página donde el usuario pueda ingresar una fórmula y al pulsar en un botón se muestre el resultado en la misma. La página debería quedar así:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Ejemplo de Scientia® Soluciones Informáticas</title>
</head>
<body>
<form id="form1" runat="server">
<asp:Panel DefaultButton="btnCalcular" runat="server">
<asp:TextBox ID="txtFormula" runat="server" />
<asp:Button ID="btnCalcular" runat="server" Text="=" OnClick="btnCalcular_Click" />
<asp:Label ID="lblResultado" runat="server" />
</asp:Panel>
</form>
</body>
</html>
Y en el code behind de ésta debería tener lo siguiente:
using System;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnCalcular_Click(object sender, EventArgs e)
{
lblResultado.Text = Formula.Resolver(txtFormula.Text).ToString();
}
}
}
Espero que este artículo les sea de utilidad y, como siempre, les dejo el proyecto de ejemplo para Visual Studio® 2008.![]()
Reciente







Domingo, 15 Noviembre, 2009 a las 22:40
Coño que cosa tan complicada carajo!
Viernes, 20 Noviembre, 2009 a las 12:35
Estimado Gustavo,
Hoy he estado trabajando con su código y básicamente he creado la clase en el namespace de mi proyecto y pegado sú codigo pero me sale una excepción, seguro que estoy haciendo algo mal.
la excepcion que me aparece es la siguiente : No se puede cargar el archivo o ensamblado ‘file:///C:\Users\Carlos\AppData\Local\Temp\rctk1y4p.dll’ ni una de sus dependencias. El sistema no puede encontrar el archivo especificado.
Este es el código de la clase, estoy trabajando en VS2008: Espero no molestarlo.
muchas gracias
saludos
Carlos de la B.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom.Compiler;
using System.Reflection;
namespace wrappermanager
{
public static class ClassCalculus
{
//codigo por: Gustavo Cantero
//http://www.programandoamedianoche.com
public static object Resolver(string Formula)
{
//Parámetros del compilador
CompilerParameters objParametros = new CompilerParameters()
{
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = false
};
//Clase
string strClase = “”;
strClase = “using System;” + “namespace wrappermanager {” + “public class ClassCalculus {” + “public object Resolver() {” + “return ” + Formula + “;}}}”;
//Compilo todo y ejecuto el método
CodeDomProvider objCompiler = CodeDomProvider.CreateProvider(“CSharp”);
CompilerResults objResultados = objCompiler.CompileAssemblyFromSource(objParametros, strClase);
object objClase = objResultados.CompiledAssembly.CreateInstance(“wrappermanager.Formula”, false, BindingFlags.CreateInstance, null, null, null, null);
return objClase.GetType().InvokeMember(“Resolver”, BindingFlags.InvokeMethod, null, objClase, null);
}
}
}
Viernes, 20 Noviembre, 2009 a las 16:50
Carlos:
Creo que el problema está en que tanto la clase precompilada (ClassCalculus) como la que compilas en tiempo de ejecución tienen el mismo nombre de namespace, clase y método, por lo tanto el CLR no sabe que ensamblado utilizar. Prueba cambiando el namespace de la clase a crear, por ejemplo, por “wrappermanagerDyn” tanto en el string (strClase) como al momento de crear la instancia con el método CreateInstance.
Por favor cuéntame si funcionó.
Saludos.
Viernes, 20 Noviembre, 2009 a las 17:14
Muchas Gracias por la Respuesta Gustavo,
He cambiado el namespace de la clase linea strClase, ha quedado así.
strClase = “using System;” + “namespace wrappermanagerDyn {” + “public static ClassCalculus {” + “public object Resolver() {” + “return ” + Formula + “;}}}”
También he cambiado el namespace en la linea del CreateInstance, ha quedado así.
object objClase = objResultados.CompiledAssembly.CreateInstance(“wrappermanagerDyn.ClassCalculus”, false, BindingFlags.CreateInstance, null, null, null, null);
y aparece el error nuevamente. El error se produce en tiempo de ejecución. y señala la linea,
object objectClase = …
Estoy instanciando está clase desde otra, la cual toma la formula desde un textbox.
Muchas Gracias por su ayuda.
saludos
Carlos de la Barrera.
Viernes, 20 Noviembre, 2009 a las 17:27
Carlos:
El otro punto que veo que, según creo, está erroneo es que la clase a crear dinámicamente es del tipo “static”, por lo cual no se puede instanciar. Prueba utilizando esta linea (es igual a la tuya pero sin el static) y cuéntame como te fue.
strClase ="using System;" +
"namespace wrappermanagerDyn {" +
"public class ClassCalculus {" +
"public object Resolver() {" +
"return " +
Formula +
";}}}";
Saludos.
Viernes, 20 Noviembre, 2009 a las 17:48
Hola Gustavo,
Lo he probado y el resultado es el mismo.
Saludos
Carlos.
Viernes, 20 Noviembre, 2009 a las 17:57
Carlos:
Acabo de probar tu clase con las modificaciones que te comenté y a mi me funciona bien. Te dejo aquí el proyecto de ejemplo pero utilizando tu clase para que lo pruebes.
Por favor comentame como te fue.
Saludos.
Viernes, 20 Noviembre, 2009 a las 18:16
Hola Gustavo,
He probado el proyecto que me ha enviado y funciona perfectamente, no se que estare haciendo mal con el mío.
Estare escarbando en los códigos y comparando las cosas que ha hecho y que hice.
Muchas Gracias por el Support.
Saludos ooordiales desde Barcelona
Carlos de la Barrera
Lunes, 30 Noviembre, 2009 a las 11:27
Buenas,
estuve peleando la semana pasada con el código, ya que como a Carlos, a mí me generaba el mismo error. Finalmente lo solucioné añadiendo a la colección ReferencedAssemblies del objeto objParametros todas las librerias necesarias ( objParametros.Add(“System.dll”) )
Espero sirva de ayuda,
Gracias
Lunes, 30 Noviembre, 2009 a las 11:29
Muchas gracias por tu aporte, Eduard!
Saludos.
Lunes, 30 Noviembre, 2009 a las 11:42
Buenas,
de nada! Sólo corregir un error, en el ejemplo que dí, es objParametros.ReferencedAssemblies.Add(“System.dll”), por supuesto.