2009-11-10 3 views
20

Y at-il un moyen de convertir la représentation de chaîne de lambda à un lambda Func? J'ai essayé Dynamic LINQ mais cela ne fonctionne pas comme prévu - par exemple, il ne s'attend pas à la syntaxe lambda =>.Parse chaîne à C# lambda Func

Résumé des réponses:

  • écrire mon propre compilateur C# - très drôle
  • tir jusqu'à compilateur externe (comme csc.exe) - très lent
  • utilisant DLINQ - comme je le disais, je n » t voir comment il peut analyser lambda expressions

Pourquoi ai-je besoin: parce qu'il n'y a aucun moyen de passer lambdas à des attributs personnalisés comme

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

donc comme solution que je voudrais passer lambda sous forme de chaîne: "rôle => role.CanDoThis & & role.AllowedCount> 5". Mais il me semble que je devrais utiliser DLINQ comme ceci: "CanDoThis & & AllowedCount> 5" - puisque c'est la syntaxe qu'il comprend. Mais ma question portait sur les vrais lambdas, j'ai déjà utilisé DLINQ au moment de poser la question.

+0

Pourquoi vous vous inquiétez de ce compilateur mettant le feu au serait lent? Vous pouvez mettre en cache l'expression résultante. – erikkallen

+0

Il semble que C# 5 viendra avec quelque chose à faire exactement ce que vous voulez. Jetez un oeil à une vidéo de PDC 2008 où Anders Hejlsberg parle de l'avenir de C#. –

+0

Je suis en attente pour C# 4.0 à libérer ... C# 5 est ;-) Je dois vraiment trop loin cette fonctionnalité pour lambdas dans les attributs. Hope 4.0 l'aura (ainsi que des attributs génériques). – queen3

Répondre

6

Vous pouvez analyser la chaîne et créer une expression lambda à l'aide de la classe Expression, en dupliquant essentiellement la fonction du compilateur.

+3

Je suppose que ça pourrait être amusant de passer du temps dessus mais pas pour mes clients - ils ne me payent pas pour écrire le compilateur C#. – queen3

+13

Quels clients étranges. :) –

1

Vous pourriez faire quelque chose avec CSharpCodeProvider (encapsuler l'expression avec du code supplémentaire pour créer une classe valide et la compiler dans un assemblage, puis charger l'assemblage).

Je crois que c'est ainsi que le fait LINQPad.

+0

Je sais comment appeler csc.exe, et j'ai l'habitude de faire de la compilation à la volée en utilisant CodeDom, c'est trop de surcharge - dans mon expérience, il fonctionne réellement csc.exe (quand je l'ai utilisé sur .NET 1.1) . – queen3

5

Je suppose que vous devez recourir à CSharpCodeProvider. Cependant, traiter toutes les références de variables locales possibles peut ne pas être trivial. Et comment diriez-vous au CSharpCodeProvider sur le type du paramètre lambda? Je serais probablement créer une classe de modèle qui ressemble à ceci:

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

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

faire quelque chose comme ceci:

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); 

Notez, cependant, que pour les applications de longue durée, vous devez créer tous ces en mémoire assemblées dans un domaine d'application distinct car elles ne peuvent pas être libérées tant que le domaine d'application dans lequel elles résident n'est pas chargé.

+1

Oui et alors je vais avoir csc.exe en cours d'exécution et AppDomains seulement pour quelques lambdas ... Cela me rappelle mon programme Turbo Pascal qui a permis à l'utilisateur d'entrer des expressions ... et a dû être déployé avec le compilateur Turbo Pascal; -) – queen3

+0

D'un autre côté - il y avait quelque chose appelé 'méthodes dynamiques'. Une façon plus légère de gérer des situations comme celles-ci. Malheureusement, je ne les ai jamais utilisés. :/ –

+0

@queen: Est-ce important? Vos clients auront déjà déployé le csc, car il fait partie du framework. En outre, le but de l'exécution dans des domaines d'application distincts est que vous pouvez récupérer la valeur puis supprimer le domaine. Votre client peut acheter un certain nombre de nouveaux serveurs pour gérer le calcul de l'argent que vous économisez en ne mettant pas en œuvre un compilateur vous-même. – erikkallen

7

Il existe de nombreux analyseurs d'expression lambda disponibles. Certains d'entre eux sont Lambda-Parser, Sprache

Exemple de code:

Example1: concat chaîne et nombre calculate:

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 a déménagé: http://github.com/sprache/sprache - Bravo! –

1

En réponse à votre problème plus spécifique, (et vous savent déjà cela, mais je » J'essaierai de le mentionner de toute façon), vous pouvez créer un dictionnaire qui mappe des valeurs qui peuvent être une constante (entiers ou enums) à 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...