sábado, 14 de noviembre de 2009 a las 19:13hs por Gustavo Cantero (The Wolf)
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 { /// /// Resuelve el valor de una fórmula /// ///Fórmula a resolver /// Resultado /// double Resultado = Formula.Resolver("2 * 8 + 3"); 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" %> <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:textbox> <asp:button ID="btnCalcular" runat="server" Text="=" OnClick="btnCalcular_Click"></asp:button> <asp:label ID="lblResultado" runat="server"></asp:label> </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.
Categoria .NET Framework | Etiquetas: Compilador, Reflection
Coño que cosa tan complicada carajo!
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
//https://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);
}
}
}
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.
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.
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.
Hola Gustavo,
Lo he probado y el resultado es el mismo.
Saludos
Carlos.
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.
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
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
Muchas gracias por tu aporte, Eduard!
Saludos.
Buenas,
de nada! Sólo corregir un error, en el ejemplo que dí, es objParametros.ReferencedAssemblies.Add(«System.dll»), por supuesto.
[…] en tiempo de ejecución Publicado el 21 enero, 2011 por mdnrbls Basado en la entrada Cómo crear una clase dinamicamente y ejecutarla de Gustavo Cantero del blog […]
Excelente artículo; por afición lo estudie y he pasado dos días muy entretenido, aunque es mi primer acercamiento a la programación quizás a alguien le sirva lo siguiente: se requiere descargar e instalar las versiones mas nuevas de C# y IIS, visual studio mandan error de version al editar los archivos y me pareció muy complicado, pero lo edite con notepad y compile en linea de comando fácilmente una vez que incluí el path del compilador en variables del sistema, mando errores similares pero indicando donde se encuentra la dll generada con la opcion /t del compilador en el archivo web.config en opción quedo listo, lo hice en 3 equipos para garantizar que funcionaba y la primera vez donde mas tarde fue en instalar y crear el sitio en IIS.
Muchas gracias por tu aporte, Edmundo!
¿Por casualidad no tienes un ejemplo en VB net? y una pregunta ¿puede usarse vbscript en este ejemplo?
No tengo un ejemplo para VB.Net, pero no debería ser muy dificil de crear, sólo debes hacer que en lugar de crear una clase con la sintaxis de C# la cree con la de VB.NET y reemplazar la linea que dice
por
En vbscript es aún más facil, simplemente debes utilizar la función Eval para que te devuelva el resultado de lo que quieras ejecutar, por ejemplo, en el código que escribí se le pasa una fórmula a la clase para que la ejecute y devuelva el resultado, en VBScript sólo hay que hacer algo así:
Suerte!
buenos días quería pedirte ayuda con interop. lo que pasa es lo siguiente.
Me pidieron que en una tabla pueda agregar en un campo el nombre y el tipo de un atributo. ej.
NombreCliente – string
EdadCliente – int
DocumentoIndentidad -string
y en un formulario en tiempo de ejecución se me pueda crear esos registros de la tabla como atributos en el formulario o en una clase pero que sea en tiempo de ejecución.
Esto con la finalidad de que la clase clientes sea dinamica y si le agrego registros en la tabla estos se puedan crear como atributos en mi formulario.
saludos
a la espera de tu respuesta
Hola, Erik.
Creo que deberías hacer dos tablas, una para la definición de los campos y otra para los valores, pero no entiendo la duda que tenés. Te pido que me detalles cuál es la consulta.
Saludos.
Hola Amigo tengo una pregunta.
Tengo una tabla X en SQL, esta tabla puede mutar, como puede crecer en columnas o disminuir. Cual seria la mejor opcion a tu parecer para crear una clase dinamicamente de esta tabla y poder trabajar con ella.
No es que quiera el codigo hecho ya bastante has hecho con este magnifico aporte , solo ideas para poder implementar algo solido.
Hola, Edgar.
Yo creo que para eso te conviene utilizar la clase ExpandoObject, que te permite agregarle propiedades a un objeto del tipo dynamic.
Por ejemplo, podrías hacer algo así:
Fijate que las propiedades Name, Age y TeamSize no existen en la definición de la clase ExpandoObject, sino que las crea dinámicamente.
Espero te sirva.
Suerte!
Buenos Días
Tengo una pregunta , y si quisiera hacer solo una linea de código y que se ejecute dentro de la misma clase y no en otra ?
Hola, David.
Tu clase ya está compilada e instanciada (es donde se está ejecutando), por lo cual, no podés cambiarla.
¿Cuál es el motivo de necesitar ejecutar el código dentro de tu clase?
Saludos.
lo que pasa es que estoy haciendo un fileupload de manera en que solo me toque subir la dirección y se cargue automáticamente , de manera que para que cargue el nombre estoy haciendo lo siguiente :
definí tres archivos todos con el mismo nombre y la única diferencia el numero final
string NombreControl = Sarlaft.ID;
string Num = NombreControl.Replace(«FSarlaft»,»»);
string NombreSarlaft= «LbSarlaft»+Num+».Text=»+serverFileName+»;»;
Donde sarlaft es el nombre del input file y num traeria el numero final , lo que quiero es ejecutar el string NombreSarlaft
Entonces podrías hacer algo así (reemplazando el tipo «MiPagina» por la clase de tu página):
y pasarle por parámetro una referencia de tu página:
Espero te sirva.
Saludos.
Hola, ando con un problemita. Yo lo que quiero hacer es llamar con un «String» a un metodo o clase. Las Clases y los métodos que llamare ya existen en el proyecto. La idea en general es que tengo una tabla que va a contener el nombre del metodo o clase que va a ejecutar.
ayuda porfa 🙂
Hola, Lorena.
No podes «llamar» a una clase, pero si a sus métodos.
Para esto podrías utilizar reflection, haciendo algo como esto:
Seguro con esto podés hacer lo que necesitás.
Por favor después contame si te sirvió.
Saludos.
Hola.
Estoy teniendo un problema al generar código dinámicamente. Recibo el mismo error que el que indica este mensaje al principio: «No se puede cargar el archivo o ensamblado…»
Lo raro es que el códgio está en una dll (Capa BL) que es referenciada desde una aplicación Windows Form y desde un capa de Servicios Web.
Cuando la llamo desde la aplicación Windows Forms anda bien.
Cuando la llamo desde la aplicación de servicios Web me tira este error.
El código que uso es el siguiente:
_Fila es un parámetro que le paso en tiempo de ejecución del tipo TablaGeneracion (definido en la misma BL)
Si por ejemplo paso como código lo siguiente:
Debería compilarme el siguiente código:
Desde la aplicación Winforms anda perfecto, pero cuando se invoca desde la aplicación de sevicios aparece el error.
Ya verifiqué que estén todas las referencias bien.
Alguna ayuda?
Gracias
Muy excelente aporte, un 10
¡Muchas gracias!
Muy buen aporte, Gracias.
Tengo una pregunta, es posible crear variables del tipo de este tipo de clase creada.
Hola, buenas tardes.
No entiendo tu pregunta, la variable
objClase
del ejemplo es una instancia de la clase creada. ¿Te referís a eso?Saludos.