Domanda

È possibile valutare quanto segue in C # in fase di esecuzione

Ho una classe che contiene 3 proprietà ( Field , Operator , Value )

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

questa è la mia classe di regole ...

Ora ho un ciclo

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

Semplicemente non conosco la sintassi, o se è possibile in C #, so che in JS è possibile ma non è un linguaggio compilato.

Aggiorna

In sostanza voglio un modo per eval (stringCode) o un modo migliore e più supportato.

È stato utile?

Soluzione

Non sono del tutto sicuro di quello che stai dicendo. Puoi provare a chiarirlo un po '?

Desideri prendere un'espressione stringa e valutarla in fase di esecuzione in C #? In tal caso la risposta è no. C # non supporta questi tipi di valutazione dinamica.

Altri suggerimenti

No, C # non supporta direttamente nulla di simile.

Le opzioni più vicine sono:

  • Crea un programma C # valido e compilalo in modo dinamico con < code> CSharpCodeProvider .
  • Crea un albero delle espressioni , compilarlo ed eseguirlo
  • Esegui la valutazione tu stesso (questo potrebbe effettivamente essere più semplice, a seconda dei tuoi operatori ecc.)

Dovresti usare le librerie CodeDOM o creare un albero delle espressioni, compilarlo ed eseguirlo. Penso che costruire l'albero delle espressioni sia l'opzione migliore.

Ovviamente potresti inserire un'istruzione switch sul tuo operatore, il che non è male perché c'è un numero limitato di operatori che puoi usare comunque.

Ecco un modo per farlo con gli alberi delle espressioni (scritti in LINQPad):

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

Un design migliore per te sarebbe che la tua regola applicasse il test stesso (o ad un valore arbitrario)

In questo modo con le istanze di Func otterrai la massima flessibilità, in questo modo:

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

allora il tuo test specifico sarebbe qualcosa del genere per un controllo del tipo forte al momento della compilazione:

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

Per una forma riflessiva

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 +"'");
    }
}   

Se vuoi essere veramente introspettivo e gestire qualsiasi valore letterale senza saperlo in fase di compilazione, puoi utilizzare CSharpCodeProvider per compilare una funzione assumendo qualcosa del tipo:

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

Questo è ovviamente un enorme buco di sicurezza, quindi chiunque può fornire il codice per questo deve essere pienamente attendibile. Ecco un'implementazione piuttosto limitata per le tue esigenze specifiche (nessun controllo di integrità)

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

Affinché ciò funzioni con tipi arbitrari, dovresti fare un po 'più di riflessione per trovare l'assembly di destinazione per il tipo per fare riferimento a esso nei parametri del compilatore.

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

Tutto il codice sopra è semplicemente una prova di concetto, manca di controllo della sanità mentale e presenta seri problemi di prestazioni.

Se si desidera essere ancora più fantasiosi, è possibile utilizzare Reflection.Emit con le istanze DynamicMethod per farlo (utilizzando operatori appropriati anziché le istanze di confronto predefinite) ma ciò richiederebbe una gestione complessa per i tipi con operatori sovrascritti.

Rendendo il tuo codice di controllo altamente generico, potresti includere più test in futuro, se necessario. Isolare essenzialmente la parte del codice che si preoccupa solo di una funzione da t - > vero / falso dal codice che fornisce queste funzioni.

Disclaimer : sono il proprietario del progetto Eval Expression.NET

Questa libreria è vicina ad essere l'equivalente di JS Eval. Puoi quasi valutare e compilare tutto il linguaggio C #.

Ecco un semplice esempio usando la tua domanda, ma la libreria va ben oltre questo semplice scenario.

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

CSharpCodeProvider ; Istruzioni switch che scelgono i diversi "operatori" appropriati il DLR ... sono tutti modi in cui puoi farlo; ma mi sembrano strane soluzioni.

Che ne dici di usare solo i delegati?

Supponendo che il tuo Field e Value siano numeri, dichiara qualcosa del genere:

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

Ora puoi definire la tua 'regola' come, ad esempio, un mucchio di lambda:

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

Puoi creare matrici di regole e applicarle come preferisci.

IEnumerable<Rule> items = ...;

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

Se Field e Values ?? non sono numeri o il tipo dipende dalla regola specifica, è possibile utilizzare oggetto anziché decimale e con un po 'di casting puoi far funzionare tutto.

Non è un progetto definitivo; è solo per darti alcune idee (ad esempio, probabilmente la classe valuterà il delegato da solo tramite un metodo Check () o qualcosa del genere).

È possibile recuperare il campo per riflessione. Quindi implementare gli operatori come metodi e utilizzare la riflessione o alcuni tipi di mapping enum-delegate per chiamare gli operatori. Gli operatori dovrebbero avere almeno 2 parametri, il valore di input e il valore che si sta utilizzando per testare con.

Anche se è vero che probabilmente non troverai un modo elegante per valutare al volo il codice C # completo senza l'uso del codice di compilazione dinamica (che non è mai carino), puoi quasi certamente valutare le tue regole in breve tempo usando il DLR (IronPython, IronRuby, ecc.) o una libreria di valutatori di espressioni che analizza ed esegue una sintassi personalizzata. Ce n'è uno, Script.NET, che fornisce una sintassi molto simile a C #.

Dai un'occhiata qui: Valutazione Espressioni a runtime in .NET (C #)

Se hai il tempo / la tendenza a imparare un po 'di Python, IronPython e il DLR risolveranno tutti i tuoi problemi: Estensione della tua app con IronPython

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top