2009-11-10 18 views
20

¿Hay alguna forma de convertir la representación de cadena de lambda a una función lambda?Parse cadena a C# lambda Func

Func<Product, bool> func = Parse<Product, bool>("product => product.Name.Length > 0"); 

me trataron dinámico LINQ pero no funciona como se esperaba - por ejemplo, que no espera que la sintaxis lambda =>.

Resumen de las respuestas:

  • escribir mi propio compilador de C# - muy divertido
  • disparar hasta compilador externo (como csc.exe) - muy lento
  • usando DLINQ - como he dicho yo no' t ver cómo se puede analizar lambda expresiones

¿Por qué necesito esto: porque no hay manera de pasar lambdas a los atributos personalizados como

[Secure(role => role.CanDoThis && role.AllowedCount > 5)] 

Así como una solución que me gustaría pasar lambda como cadena: "role => role.CanDoThis & & role.AllowedCount> 5". Pero parece que tendré que usar DLINQ así: "CanDoThis & & AllowedCount> 5" - ya que esa es la sintaxis que entiende. Pero mi pregunta era sobre las verdaderas lambdas, ya he usado DLINQ al momento de preguntar.

+0

¿Por qué se preocupan por que para prender el compilador sería lento? Puede almacenar en caché la expresión resultante. – erikkallen

+0

Parece que C# 5 vendrá con algo para hacer exactamente lo que usted desea. Eche un vistazo a un video de PDC 2008 donde Anders Hejlsberg habla sobre el futuro de C#. –

+0

Estoy esperando que se publique C# 4.0 ... C# 5 está demasiado lejos ;-) Realmente necesito esta característica para lambdas en atributos. Hope 4.0 lo tendrá (así como los atributos genéricos). – queen3

Respuesta

6

Puede analizar la cadena y crear una expresión lambda utilizando la clase Expression, esencialmente duplicando la función del compilador.

+3

Supongo que podría ser divertido pasar tiempo pero no para mis clientes, no me pagan para escribir el compilador de C#. – queen3

+13

¡Qué clientes tan extraños! :) –

1

Es posible que pueda hacer algo con CSharpCodeProvider (ajuste la expresión con más código para crear una clase válida y compilarla en un ensamblaje, luego cargue el ensamblaje).

Creo que es así como LINQPad lo hace.

+0

Sé cómo invocar csc.exe y tengo experiencia en hacer compilaciones sobre la marcha usando CodeDom, eso es demasiada sobrecarga - en mi experiencia realmente ejecuta csc.exe (cuando lo usé en .NET 1.1) . – queen3

5

Supongo que tiene que recurrir al CSharpCodeProvider. Sin embargo, lidiar con todas las posibles referencias de variables locales podría no ser trivial. ¿Y cómo le diría a CSharpCodeProvider sobre el tipo del parámetro lambda? Yo probablemente crear una clase de plantilla con este aspecto:

class ExpressionContainer { 
    public Expression<Func<Product, bool>> TheExpression; 
    public string Length; 

    public ExpressionContainer() { 
     TheExpression = <user expression text>; 
    } 
} 

luego hacer algo como esto:

string source = <Code from above>; 
Assembly a; 
using (CSharpCodeProvider provider = new CSharpCodeProvider(...) { 
    List<string> assemblies = new List<string>(); 
    foreach (Assembly x in AppDomain.CurrentDomain.GetAssemblies()) { 
     try { 
      assemblies.Add(x.Location); 
     } 
     catch (NotSupportedException) { 
      // Dynamic assemblies will throw, and in .net 3.5 there seems to be no way of finding out whether the assembly is dynamic before trying. 
     } 
    } 

    CompilerResults r = provider.CompileAssemblyFromSource(new CompilerParameters(assemblies.ToArray()) { GenerateExecutable = false, GenerateInMemory = true }, source); 
    if (r.Errors.HasErrors) 
     throw new Exception("Errors compiling expression: " + string.Join(Environment.NewLine, r.Errors.OfType<CompilerError>().Select(e => e.ErrorText).ToArray())); 
    a = r.CompiledAssembly; 
} 
object o = a.CreateInstance("ExpressionContainer"); 
var result = (Expression<Func<Product, bool>>)o.GetType().GetProperty("TheExpression").GetValue(o); 

Tenga en cuenta, sin embargo, que para aplicaciones de larga duración, se debe crear todo esto en memoria ensamblados en un dominio de aplicación separado, ya que no se pueden liberar hasta que se descargue el dominio de aplicación en el que residen.

+1

Sí y luego ejecutaré csc.exe y AppDomains solo para un par de lambdas ... Esto me recuerda mi programa Turbo Pascal que permitió al usuario ingresar expresiones ... y tuvo que ser implementado junto con el compilador de Turbo Pascal; -) – queen3

+0

Por otro lado, había algo llamado 'métodos dinámicos'. Una forma más liviana de manejar situaciones como estas. Desgraciadamente, nunca los he usado. :/ –

+0

@queen: ¿Importa? Sus clientes ya tendrán implementado el ccc, ya que es parte del marco. Además, el propósito de ejecutar aplicaciones separadas es que puedes recuperar el valor y luego eliminar el dominio de la aplicación. Su cliente puede comprar bastantes servidores nuevos para manejar el cálculo del dinero que ahorra al no implementar un compilador usted mismo. – erikkallen

7

Hay muchos analizadores de expresiones lambda disponibles. Algunos de ellos son Lambda-Parser, Código Sprache

muestra:

Ejemplo 1: concat cadena y el número calcular:

string code = "2.ToString()+(4*2)"; // C# code Func<string> 
func = ExpressionParser.Compile<Func<string>>(code); // compile code 
string result = func(); // result = "28" 
+2

Sprache se mudó: http://github.com/sprache/sprache - ¡salud! –

1

respuesta a su problema más específico, (y ustedes ya saben esto, pero yo' Trataré de mencionarlo de todos modos), podrías crear un diccionario que asigne valores que pueden ser una constante (enteros o enumeraciones) a lambdas.

sealed class Product { 
    public bool CanDoThis { get; set; } 
    public int AllowedCount { get; set; } 
} 

public enum SecureFuncType { 
    Type1, 
    Type2, 
    Type3 
} 

sealed class SecureAttribute : Attribute { 
    [NotNull] readonly Func<Product, bool> mFunc; 

    public SecureAttribute(SecureFuncType pType) { 
     var secureFuncs = new Dictionary<SecureFuncType, Func<Product, bool>> { 
     { SecureFuncType.Type1, role => role.CanDoThis && role.AllowedCount > 1 }, 
     { SecureFuncType.Type2, role => role.CanDoThis && role.AllowedCount > 2 }, 
     { SecureFuncType.Type3, role => role.CanDoThis && role.AllowedCount > 3 } 
     }; 

     mFunc = secureFuncs[pType]; 
    } 
} 

[Secure(SecureFuncType.Type1)] 
sealed class TestClass { 
} 

// etc...