2009-11-10 9 views
20

Gibt es eine Möglichkeit, String Darstellung von Lambda zu einem Lambda Func zu konvertieren?Parse String zu C# Lambda Func

Ich habe versucht, Dynamic LINQ, aber es funktioniert nicht wie erwartet - zum Beispiel erwartet es nicht Lambda-Syntax =>.

Zusammenfassung der Antworten:

  • meine eigenen C# Compiler zu schreiben - sehr lustig
  • externen Compiler Anheizen (wie csc.exe) - sehr langsam
  • DLINQ mit - wie ich sagte, ich don‘ t sehen, wie es Lambda-Ausdrücke

analysieren kann, warum ich dies tun müssen: weil es keine Möglichkeit gibt, Lambda-Ausdrücke zu passieren, um benutzerdefinierte Attribute wie

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

Um dieses Problem zu umgehen Ich mag würde Lambda als String zu übergeben: "role => role.CanDoThis & & role.AllowedCount> 5". Aber ich muss DLINQ so verwenden: "CanDoThis & & AllowedCount> 5" - das ist die Syntax, die es versteht. Aber meine Frage war echte Lambdas. Ich habe DLINQ bereits zum Zeitpunkt des Fragens benutzt.

+0

Warum Sie sich Sorgen über, dass der Compiler langsam wäre Anheizen? Sie können den resultierenden Ausdruck zwischenspeichern. – erikkallen

+0

Es scheint, dass C# 5 mit etwas zu tun kommt genau das, was Sie wollen. Schauen Sie sich ein Video von der PDC 2008 an, auf dem Anders Hejlsberg über die Zukunft von C# spricht. –

+0

Ich warte auf C# 4.0 veröffentlicht zu werden ... C# 5 ist viel zu weit weg ;-) Ich brauche diese Funktion für Lambdas in Attributen. Hope 4.0 wird es (sowie generische Attribute) haben. – queen3

Antwort

6

Sie könnten die Zeichenfolge syntaktisch analysieren und einen Lambda-Ausdruck mit der Expression-Klasse erstellen, der im Wesentlichen die Funktion des Compilers verdoppelt.

+3

Ich denke, es könnte Spaß machen, Zeit zu verbringen darauf aber nicht für meine Kunden - sie zahlen mir nicht, C# -Compiler zu schreiben. – queen3

+13

Was für ein seltsamer Kunde. :) –

1

Möglicherweise können Sie etwas mit CSharpCodeProvider tun (den Ausdruck mit etwas mehr Code umschließen, um eine gültige Klasse zu erstellen und in eine Assembly zu kompilieren und dann die Assembly zu laden).

Ich glaube, das ist wie LINQPad tut es.

+0

Ich weiß, wie csc.exe aufrufen, und habe Erfahrung mit On-The-Fly-Kompilierung mit CodeDom, das ist zu viel Aufwand - nach meiner Erfahrung führt es tatsächlich csc.exe (wenn ich es auf .NET 1.1) . – queen3

5

Ich denke, Sie müssen auf den CSharpCodeProvider zurückgreifen. Der Umgang mit allen möglichen lokalen Variablenverweisen ist jedoch möglicherweise nicht trivial. Und wie würden Sie dem CSharpCodeProvider den Typ des Lambda-Parameters mitteilen? Ich würde wahrscheinlich eine Template-Klasse erstellen wie folgt aussehen:

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

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

Dann so etwas tun:

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

Beachten Sie jedoch, dass für lang laufende Anwendungen sollten Sie alle diese im Speicher erstellen Assemblys in einer separaten Anwendungsdomäne, da sie nicht freigegeben werden können, bis die Anwendungsdomäne, in der sie sich befinden, entladen wird.

+1

Ja und dann habe ich csc.exe laufen und AppDomains nur für ein paar Lambdas ... Das erinnert mich an mein Turbo Pascal-Programm, mit dem Benutzer Ausdrücke eingeben ... und musste zusammen mit Turbo Pascal-Compiler bereitgestellt werden; -) – queen3

+0

Auf der anderen Seite - gab es etwas namens "dynamische Methoden". Ein leichterer Weg, mit solchen Situationen umzugehen. Unglücklicherweise habe ich sie nie benutzt. :/ –

+0

@queen: Ist es wichtig? Ihre Kunden haben bereits CSC bereitgestellt, da es Teil des Frameworks ist. Der gesamte Zweck der Ausführung in separaten Anwendungsdomänen besteht darin, dass Sie den Wert abrufen und dann die Anwendungsdomäne abreißen können. Ihr Kunde kann einige neue Server kaufen, um die Berechnung für das eingesparte Geld zu verwalten, indem er selbst keinen Compiler implementiert. – erikkallen

7

Sie sind viele Lambda Ausdruck Parser verfügbar. Einige von ihnen sind Lambda-Parser, Sprache

Beispielcode:

Example1: string concat und Anzahl berechnen:

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 ist umgezogen: http://github.com/sprache/sprache - Prost! –

1

zu Ihrem spezifischere Problem reagieren, (und Sie können bereits wissen, aber ich (lch versuche, es trotzdem zu erwähnen), du könntest ein Wörterbuch erstellen, das Werte, die eine Konstante sein können (Integer oder Enums), auf Lambdas abbildet.

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...