2009-10-15 18 views
5

¿es posible crear un método anónimo en C# desde una cadena?Crear método anónimo a partir de una cadena en C#

p. Ej. si tengo una cadena "x + y * z" ¿es posible convertir esto en algún tipo de método/objeto lambda que pueda llamar con los parámetros arbitrarios x, y, z?

+0

Estoy bastante seguro de que un tipo en Microsoft hizo una transmisión sobre .net 4 que tiene el compilador disponible como un servicio para lograr cosas como esta. No sé si es su caso sin embargo. –

+1

Anders estaba hablando de un trabajo de futuro muy lejano cuando hablaba de "compilador como servicio" en PDC. C# 4 ciertamente no tendrá ese trabajo. –

+1

Duplicado: http://stackoverflow.com/questions/1437964/best-and-shortest-way-to-evaluate-mathematical-expressions/ Esa pregunta también fue respondida más específicamente que esta. – Joren

Respuesta

13

Es posible, sí. Debe analizar la cadena y, por ejemplo, compilar un delegado utilizando árboles de expresiones.

Aquí es un ejemplo de creación (x, y, z) => x + y * z utilizando árboles de expresión:

ParameterExpression parameterX = Expression.Parameter(typeof(int), "x"); 
ParameterExpression parameterY = Expression.Parameter(typeof(int), "y"); 
ParameterExpression parameterZ = Expression.Parameter(typeof(int), "z"); 
Expression multiplyYZ = Expression.Multiply(parameterY, parameterZ); 
Expression addXMultiplyYZ = Expression.Add(parameterX, multiplyYZ); 
Func<int,int,int,int> f = Expression.Lambda<Func<int, int, int, int>> 
(
    addXMultiplyYZ, 
    parameterX, 
    parameterY, 
    parameterZ 
).Compile(); 
Console.WriteLine(f(24, 6, 3)); // prints 42 to the console 
+0

+1 Este es un buen ejemplo, pero se adapta a la cadena en cuestión. Este ejemplo no ayudaría al OP a analizar una cadena aleatoria, inferir los tipos de los identificadores en la cadena y crear un método basado en lo que se encontró. Sin embargo, +1 a ti por el buen ejemplo. –

+0

+1, y aquí está para 9999 ¡ya no! –

+1

Supuse que el análisis sería más familiar (al menos la literatura sobre análisis es más grande) para el OP que los árboles de expresión. El propósito del árbol de expresión es únicamente mostrarle la tecnología y mostrar su poder, no resolver el problema general. – jason

5

C# no tiene ninguna funcionalidad como esta (otros lenguajes, como JavaScript, tienen funciones eval para manejar cosas como esta). Tendrá que analizar la cadena y crear un método usted mismo con cualquiera de los árboles de expresión o emitiendo IL.

1

Podría ser posible con una gramática (por ejemplo antlr) y un intérprete que crea árboles de expresión. No es una tarea pequeña, sin embargo, puede tener éxito si limita el alcance de lo que acepta como entrada. Aquí hay algunas referencias:

aquí es lo que algunos código puede parecerse a transformar un iTree antlr en un árbol de expresión. No está completo, pero te muestra a qué te enfrentas.

private Dictionary<string, ParameterExpression> variables 
    = new Dictionary<string, ParameterExpression>(); 

public Expression Visit(ITree tree) 
{ 
    switch(tree.Type) 
    { 
     case MyParser.NUMBER_LITERAL: 
      { 
       float value; 
       var literal = tree.GetChild(0).Text; 
       if (!Single.TryParse(literal, out value)) 
        throw new MyParserException("Invalid number literal"); 
       return Expression.Constant(value); 
      } 

     case MyParser.IDENTIFIER: 
      { 
       var ident = tree.GetChild(0).Text; 
       if (!this.variables.ContainsKey(ident)) 
       { 
        this.variables.Add(ident, 
         Expression.Parameter(typeof(float), ident)); 
       } 

       return this.variables[ident]; 
      } 

     case MyParser.ADD_EXPR: 
      return Expression.Add(Visit(tree.GetChild(0)), Visit(tree.GetChild(1))); 

     // ... more here 
    } 
} 
2

Existen funciones para hacer esto en .Net framework.

No es fácil. Necesita agregar algún código alrededor de la declaración para convertirla en un ensamblaje completo que incluya una clase y un método al que pueda llamar.

Después de que pase la cadena de

CSharpCodeProvider.CompileAssemblyFromSource(options, yourcode); 

Here is an example

+0

Junto con averiguar qué parámetros son necesarios, etc. Difícil de hacer amigable con CompileAssemblyFromSource. – user7116

7

Sólo por diversión usando CodeDom (cualquier código C# válida está permitido en la cadena siempre que está presente en mscorlib (Sin verificación de errores en absoluto):

static class Program 
{ 
    static string code = @" 
     public static class __CompiledExpr__ 
     {{ 
      public static {0} Run({1}) 
      {{ 
       return {2}; 
      }} 
     }} 
     "; 

    static MethodInfo ToMethod(string expr, Type[] argTypes, string[] argNames, Type resultType) 
    { 
     StringBuilder argString = new StringBuilder(); 
     for (int i = 0; i < argTypes.Length; i++) 
     { 
      if (i != 0) argString.Append(", "); 
      argString.AppendFormat("{0} {1}", argTypes[i].FullName, argNames[i]); 
     } 
     string finalCode = string.Format(code, resultType != null ? resultType.FullName : "void", 
      argString, expr); 

     var parameters = new CompilerParameters(); 
     parameters.ReferencedAssemblies.Add("mscorlib.dll"); 
     parameters.ReferencedAssemblies.Add(Path.GetFileName(Assembly.GetExecutingAssembly().Location)); 
     parameters.GenerateInMemory = true; 

     var c = new CSharpCodeProvider(); 
     CompilerResults results = c.CompileAssemblyFromSource(parameters, finalCode); 
     var asm = results.CompiledAssembly; 
     var compiledType = asm.GetType("__CompiledExpr__"); 
     return compiledType.GetMethod("Run"); 
    } 

    static Action ToAction(this string expr) 
    { 
     var method = ToMethod(expr, new Type[0], new string[0], null); 
     return() => method.Invoke(null, new object[0]); 
    } 

    static Func<TResult> ToFunc<TResult>(this string expr) 
    { 
     var method = ToMethod(expr, new Type[0], new string[0], typeof(TResult)); 
     return() => (TResult)method.Invoke(null, new object[0]); 
    } 

    static Func<T, TResult> ToFunc<T, TResult>(this string expr, string arg1Name) 
    { 
     var method = ToMethod(expr, new Type[] { typeof(T) }, new string[] { arg1Name }, typeof(TResult)); 
     return (T arg1) => (TResult)method.Invoke(null, new object[] { arg1 }); 
    } 

    static Func<T1, T2, TResult> ToFunc<T1, T2, TResult>(this string expr, string arg1Name, string arg2Name) 
    { 
     var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2) }, 
      new string[] { arg1Name, arg2Name }, typeof(TResult)); 
     return (T1 arg1, T2 arg2) => (TResult)method.Invoke(null, new object[] { arg1, arg2 }); 
    } 

    static Func<T1, T2, T3, TResult> ToFunc<T1, T2, T3, TResult>(this string expr, string arg1Name, string arg2Name, string arg3Name) 
    { 
     var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2), typeof(T3) }, 
      new string[] { arg1Name, arg2Name, arg3Name }, typeof(TResult)); 
     return (T1 arg1, T2 arg2, T3 arg3) => (TResult)method.Invoke(null, new object[] { arg1, arg2, arg3 }); 
    } 

    static void Main(string[] args) 
    { 
     var f = "x + y * z".ToFunc<int, int, long, long>("x", "y", "z"); 
     var x = f(3, 6, 8); 

    } 
} 
Cuestiones relacionadas