2009-07-30 14 views
3

Ist es möglich, die folgenden in C# zur Laufzeiteval (string) zu C# -Code

Ich habe eine Klasse zu bewerten, die drei Eigenschaften enthält (Field, Operator, Value)

rule.Field; 
rule.Operator; 
rule.Value; 

das ist mein Regelklasse ...

Jetzt habe ich eine Schleife

foreach(item in items) 
    { 
     // here I want to create a dynamic expression to evaluate at runtime 
     // something like 
     if (item.[rule.field] [rule.operator] [rule.value]) 
      { do work } 
    } 

Ich kenne nur die Syntax nicht, oder wenn es in C# möglich ist, weiß ich in JS seine mögliche, aber das ist keine kompilierte Sprache.

aktualisieren

Grunde möchte ich einen Weg zu eval(stringCode) oder besser mehr unterstützt Weise.

+0

Dann scheint eine Lösung mit Delegierten ziemlich nah an dem zu sein, was Sie brauchen; es sei denn, der stringCode ist wirklich dynamisch (vom Benutzer bereitgestellter Inhalt im laufenden Betrieb). Dann brauchen Sie etwas durch die DLR (die in .NET 4.0 bevorsteht und mir nicht bekannt genug ist). –

+0

Solange Ihr spezifischer Fall konstant bleibt, können Sie es erreichen. Wenn Sie die Möglichkeit hätten, * willkürliche * Logik anstelle der beschriebenen Form zu verwenden, hätten Sie Probleme. Im Wesentlichen solange die Menge aller Eingaben zu Ihrem "Code" im Voraus bekannt ist und es keine Nebeneffekte gibt außer der Rückgabe eines einzelnen Wertes (wieder wo der Typ zur Kompilierzeit bekannt ist (auch wenn nur als 'Objekt') dann ist das möglich Alles außerhalb dieses und du bist außerhalb dessen, was etwas wie 'eval' tun kann – ShuggyCoUk

+0

Ah Ich habe gerade dein Update gesehen Nein, du kannst nicht die vollen Features von der Art von Dingen bekommen, die eval in etwas wie Javascript macht Der Aufwand, mehr und mehr Funktionalität zu bekommen, würde exponentiell härter werden, da Sie mehr und mehr Aspekte wollten, die funktionieren würden, wenn Sie den Code zur Kompilierungszeit einfach in die Klasse einfügen würden wurde zu einer Instanzvariable hochgestuft und der introspizierte Laufzeitstatus war der lexikalischen Struktur der Kompilierungszeit nicht mehr ausreichend ähnlich, um dem Konzept zu ermöglichen, weiter zu arbeiten. – ShuggyCoUk

Antwort

1

Ich bin mir nicht ganz sicher, was Sie sagen. Können Sie es ein bisschen erklären?

Möchten Sie einen Zeichenfolgenausdruck verwenden und ihn zur Laufzeit in C# auswerten? Wenn ja, ist die Antwort nein. C# unterstützt solche Arten der dynamischen Auswertung nicht.

+0

Ja, genau das versuche ich zu tun. –

+0

@JL leider wird diese Art der Auswertung in C# nicht unterstützt (auch in 4.0). – JaredPar

+0

Wie ist es dann möglich, einen dynamischen Operator zu vergleichen? –

2

Sie müssten entweder die CodeDOM-Bibliotheken verwenden oder eine Ausdrucksbaumstruktur erstellen, sie kompilieren und ausführen. Ich denke, der Aufbau des Ausdrucksbaums ist die beste Option.

Natürlich könnten Sie eine switch-Anweisung für Ihren Operator eingeben, was nicht schlecht ist, da es eine begrenzte Anzahl von Operatoren gibt, die Sie trotzdem verwenden können.

Hier ist ein Weg, dies zu tun mit Ausdruck Bäume (in LINQPad geschrieben):

void Main() 
{ 
    var programmers = new List<Programmer>{ 
     new Programmer { Name = "Turing", Number = Math.E}, 
     new Programmer { Name = "Babbage", Number = Math.PI}, 
     new Programmer { Name = "Lovelace", Number = Math.E}}; 


    var rule0 = new Rule<string>() { Field = "Name", Operator = BinaryExpression.Equal, Value = "Turing" }; 
    var rule1 = new Rule<double>() { Field = "Number", Operator = BinaryExpression.GreaterThan, Value = 2.719 }; 

    var matched0 = RunRule<Programmer, string>(programmers, rule0); 
    matched0.Dump(); 

    var matched1 = RunRule<Programmer, double>(programmers, rule1); 
    matched1.Dump(); 

    var matchedBoth = matched0.Intersect(matched1); 
    matchedBoth.Dump(); 

    var matchedEither = matched0.Union(matched1); 
    matchedEither.Dump(); 
} 

public IEnumerable<T> RunRule<T, V>(IEnumerable<T> foos, Rule<V> rule) { 

     var fieldParam = Expression.Parameter(typeof(T), "f"); 
     var fieldProp = Expression.Property (fieldParam, rule.Field); 
     var valueParam = Expression.Parameter(typeof(V), "v"); 

     BinaryExpression binaryExpr = rule.Operator(fieldProp, valueParam); 

     var lambda = Expression.Lambda<Func<T, V, bool>>(binaryExpr, fieldParam, valueParam); 
     var func = lambda.Compile(); 

     foreach(var foo in foos) { 
      var result = func(foo, rule.Value); 
      if(result) 
       yield return foo; 
     } 

} 

public class Rule<T> { 
    public string Field { get; set; } 
    public Func<Expression, Expression, BinaryExpression> Operator { get; set; } 
    public T Value { get; set; } 
} 

public class Programmer { 
    public string Name { get; set; } 
    public double Number { get; set; } 
} 
8

Nein, C# nicht direkt etwas Derartiges kann leider.

Die nächsten Optionen sind:

  • Erstellen Sie eine vollständige C# Programm gültig und sie dynamisch mit CSharpCodeProvider kompilieren.
  • Bauen Sie ein expression tree, zusammenstellen und die Auswertung selbst
  • Perform ausführen (dies eigentlich am einfachsten sein kann, je nach Betreiber usw.)
0

Sie das Feld durch Reflexion abrufen können. Und dann implementieren Sie die Operatoren als Methoden und verwendet Reflexion oder einige Arten von Enum-Delegate-Mapping, um die Operatoren aufzurufen. Die Operatoren sollten mindestens 2 Parameter haben, den Eingabewert und den Wert, mit dem Sie testen möchten.

1

CSharpCodeProvider; switch Anweisungen, die die richtigen verschiedenen "Operatoren" auswählen; das DLR ... das sind alles Wege, die Sie tun können; aber sie scheinen mir merkwürdige Lösungen zu sein.

Wie wäre es mit Delegierten?

Angenommen, Ihre Field und Value Zahlen sind, so etwas wie dies erklären:

delegate bool MyOperationDelegate(decimal left, decimal right); 
... 
class Rule { 
    decimal Field; 
    decimal Value; 
    MyOperationDelegate Operator; 
} 

Jetzt können Sie Ihre 'Regel', wie zum Beispiel eine Reihe von lambdas definieren:

Rule rule1 = new Rule; 
rule1.Operation = (decimal l, decimal r) => { return l > r; }; 
rule1.Field = ... 

Sie kann Arrays von Regeln erstellen und sie beliebig anwenden.

IEnumerable<Rule> items = ...; 

foreach(item in items) 
{ 
    if (item.Operator(item.Field, item.Value)) 
    { /* do work */ } 
} 

Wenn Field und Values keine Zahlen sind, oder die Art hängt von der spezifischen Regel können Sie object statt decimal verwenden, und mit ein wenig Gießen Sie es alle Arbeit machen kann.

Das ist kein endgültiger Entwurf; es ist nur, um Ihnen einige Ideen zu geben (zum Beispiel würden Sie wahrscheinlich die Klasse haben, um den Delegierten selbst über eine Check() Methode oder etwas auszuwerten).

2

Ein besseres Design für Dich wäre für die Regel der Prüfung selbst (oder auf einen beliebigen Wert) Dadurch dies mit Func Instanzen

anwenden finden Sie die größte Flexibilität erhalten, etwa so:

IEnumerable<Func<T,bool> tests; // defined somehow at runtime 
foreach (var item in items) 
{ 
    foreach (var test in tests) 
    { 
     if (test(item)) 
     { 
      //do work with item 
     } 
    } 
} 

dann würde Ihr spezieller Test so etwas für starke Typprüfung bei der Kompilierung sein:

public Func<T,bool> FooEqualsX<T,V>(V x) 
{ 
    return t => EqualityComparer<V>.Default.Equals(t.Foo, x); 
} 

für eine reflektierende Form

public Func<T,bool> MakeTest<T,V>(string name, string op, V value) 
{ 
    Func<T,V> getter; 
    var f = typeof(T).GetField(name); 
    if (f != null)  
    { 
     if (!typeof(V).IsAssignableFrom(f.FieldType)) 
      throw new ArgumentException(name +" incompatible with "+ typeof(V)); 
     getter= x => (V)f.GetValue(x); 
    } 
    else 
    { 
     var p = typeof(T).GetProperty(name); 
     if (p == null)  
      throw new ArgumentException("No "+ name +" on "+ typeof(T)); 
     if (!typeof(V).IsAssignableFrom(p.PropertyType)) 
      throw new ArgumentException(name +" incompatible with "+ typeof(V)); 
     getter= x => (V)p.GetValue(x, null); 
    } 
    switch (op) 
    { 
     case "==": 
      return t => EqualityComparer<V>.Default.Equals(getter(t), value); 
     case "!=": 
      return t => !EqualityComparer<V>.Default.Equals(getter(t), value); 
     case ">": 
      return t => Comparer<V>.Default.Compare(getter(t), value) > 0; 
     // fill in the banks as you need to 
     default: 
      throw new ArgumentException("unrecognised operator '"+ op +"'"); 
    } 
} 

Wenn Sie wollen, ohne bei der Kompilierung zu wissen, jeden wörtlichen wirklich introspektiv und zu handhaben sein, Sie die CSharpCodeProvider verwenden könnten eine Funktion unter der Annahme, so etwas wie zu kompilieren:

public static bool Check(T t) 
{ 
    // your code inserted here 
} 

Das ist natürlich ein massive Sicherheitsloch, wer Code für diese liefern kann, muss voll vertrauenswürdig sein. Hier ist eine etwas begrenzte Implementierung für Ihre spezifischen Bedürfnisse (kein Verstand überhaupt Kontrolle)

private Func<T,bool> Make<T>(string name, string op, string value) 
{ 

    var foo = new Microsoft.CSharp.CSharpCodeProvider() 
     .CompileAssemblyFromSource(
      new CompilerParameters(), 
      new[] { "public class Foo { public static bool Eval("+ 
       typeof(T).FullName +" t) { return t."+ 
       name +" "+ op +" "+ value 
       +"; } }" }).CompiledAssembly.GetType("Foo"); 
    return t => (bool)foo.InvokeMember("Eval", 
     BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod , 
     null, null, new object[] { t }); 
} 

// use like so: 
var f = Make<string>("Length", ">", "2"); 

Für diese mit beliebigen Typen zu arbeiten, würden Sie ein bisschen mehr Reflexion zu tun haben, um die Zielanordnung zu finden für die Art zu referenzieren es in den Compiler-Parametern.

private bool Eval(object item, string name, string op, string value) 
{ 

    var foo = new Microsoft.CSharp.CSharpCodeProvider() 
     .CompileAssemblyFromSource(
      new CompilerParameters(), 
      new[] { "public class Foo { public static bool Eval("+ 
       item.GetType().FullName +" t) "+ 
       "{ return t."+ name +" "+ op +" "+ value +"; } }" 
      }).CompiledAssembly.GetType("Foo"); 
    return (bool)foo.InvokeMember("Eval", 
     BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod , 
     null, null, new object[] { item }); 
} 

Alle oben genannten Code einfach ein Proof of Concept ist, fehlt es an Verstand überprüft und ernsthafte Performance-Probleme hat.

Wenn Sie noch schicker sein möchten, können Sie Reflection.Emit mit DynamicMethod-Instanzen verwenden (indem Sie richtige Operatoren anstelle der Standardvergleichsinstanzen verwenden), aber dies würde eine komplexe Behandlung von Typen mit überschriebenen Operatoren erfordern.

Indem Sie Ihren Prüfcode hochgradig generisch machen, können Sie in Zukunft mehr Tests hinzufügen, als Sie benötigen. Im Wesentlichen isolieren Sie den Teil Ihres Codes, der nur eine Funktion betrifft, von t -> true/false aus dem Code, der diese Funktionen bereitstellt.

0

Obwohl es wahr ist, dass Sie wahrscheinlich keine elegante Möglichkeit finden werden, vollen C# -Code im laufenden Betrieb ohne die Verwendung von dynamisch kompilierendem Code zu bewerten (was nie schön ist), können Sie Ihre Regeln mit Sicherheit kurz evaluieren Bestellung entweder mit dem DLR (IronPython, IronRuby, etc.) oder einer Expression-Evaluator-Bibliothek, die eine benutzerdefinierte Syntax analysiert und ausführt. Es gibt eine, Script.NET, die eine sehr ähnliche Syntax wie C# bietet.

Werfen Sie einen Blick hier: Evaluating Expressions a Runtime in .NET(C#)

Wenn Sie die Zeit/Neigung haben ein wenig Python zu lernen, dann wird Ironpython und das DLR alle Ihre Probleme lösen: Extending your App with IronPython

2

Haftungsausschluss: Ich m der Besitzer des Projekts Eval Expression.NET

Diese Bibliothek ist in der Nähe der JS Eval gleichwertig. Sie können fast alle C# -Sprachen auswerten und kompilieren.

Hier ist ein einfaches Beispiel mit Ihrer Frage, aber die Bibliothek geht weit über dieses einfache Szenario hinaus.

int field = 2; 
int value = 1; 
string binaryOperator = ">"; 

string formula = "x " + binaryOperator + " y"; 

// For single evaluation 
var value1 = Eval.Execute<bool>(formula, new { x = field, y = value }); 

// For many evaluation 
var compiled = Eval.Compile<Func<int, int, bool>>(formula, "x", "y"); 
var value2 = compiled(field, value);