Question

Est-il possible d'évaluer les éléments suivants en C # lors de l'exécution

J'ai une classe qui contient 3 propriétés ( Champ , Opérateur , Valeur )

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

c'est ma classe de règles ...

Maintenant, j'ai une boucle

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

Je ne connais pas la syntaxe, ou si c'est possible en C #, je sais que dans JS, c'est possible, mais ce n'est pas un langage compilé.

Mettre à jour

En gros, je veux un moyen de eval (stringCode) ou un moyen plus efficace et mieux pris en charge.

Était-ce utile?

La solution

Je ne suis pas tout à fait sûr de ce que vous dites. Pouvez-vous essayer de clarifier un peu?

Voulez-vous prendre une expression de chaîne et l’évaluer au moment de l’exécution en C #? Si oui, la réponse est non. C # ne supporte pas ce type d'évaluation dynamique.

Autres conseils

Non, C # ne prend pas directement en charge ce genre de chose.

Les options les plus proches sont:

  • Créez un programme C # valide complet et compilez-le de manière dynamique avec < code> CSharpCodeProvider .
  • Créez un arbre d'expression , compilez-le et exécutez-le
  • Effectuez l'évaluation vous-même (cette opération peut s'avérer plus facile, selon vos opérateurs, etc.)

Vous devez soit utiliser les bibliothèques CodeDOM, soit créer un arbre d’expression, le compiler et l’exécuter. Je pense que la construction de l'arbre d'expression est la meilleure option.

Bien sûr, vous pouvez ajouter une instruction switch à votre opérateur, ce qui n’est pas mauvais, car le nombre d’opérateurs que vous pouvez utiliser est limité.

Voici un moyen de le faire avec des arbres d'expression (écrits dans 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; }
}

Une meilleure conception pour vous serait que votre règle applique le test lui-même (ou à une valeur arbitraire)

En faisant cela avec les instances Func, vous obtiendrez la plus grande flexibilité, comme ceci:

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

votre test spécifique ressemblera à ceci pour une vérification de type forte au moment de la compilation:

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

Pour une forme réfléchissante

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

Si vous souhaitez être vraiment introspectif et gérer n'importe quel littéral sans le savoir au moment de la compilation, vous pouvez utiliser CSharpCodeProvider pour compiler une fonction supposant quelque chose comme:

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

Il s’agit bien entendu d’une énorme faille de sécurité. Par conséquent, toute personne pouvant fournir du code doit être totalement fiable. Voici une implémentation quelque peu limitée pour vos besoins spécifiques (aucune vérification de cohérence)

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

Pour que cela fonctionne avec des types arbitraires, vous devez réfléchir un peu plus longtemps afin de trouver l'assembly cible du type à référencer dans les paramètres du compilateur.

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

Tout le code ci-dessus est simplement une preuve de concept, il manque de contrôle de cohérence et pose de sérieux problèmes de performances.

Si vous voulez être encore plus sophistiqué, vous pouvez utiliser Reflection.Emit avec des instances DynamicMethod pour le faire (en utilisant des opérateurs appropriés plutôt que les instances de comparateur par défaut), mais cela nécessiterait une gestion complexe des types avec des opérateurs substitués.

En rendant votre code de contrôle hautement générique, vous pourrez inclure plus de tests à l’avenir, selon vos besoins. Isolez essentiellement la partie de votre code qui ne concerne qu'une fonction de t - > true / false à partir du code fournissant ces fonctions.

Avertissement : je suis le propriétaire du projet Eval Expression.NET

Cette bibliothèque est proche de l’équivalent de JS Eval. Vous pouvez presque évaluer et compiler tout le langage C #.

Voici un exemple simple utilisant votre question, mais la bibliothèque va bien au-delà de ce simple scénario.

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 ; Les instructions switch qui sélectionnent les différents "opérateurs" appropriés ;; le DLR ... ce sont toutes des façons de le faire; mais ils me semblent des solutions étranges.

Pourquoi ne pas utiliser uniquement des délégués?

En supposant que votre champ et votre valeur soient des nombres, déclarez quelque chose comme ceci:

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

Vous pouvez maintenant définir votre "règle" comme, par exemple, un groupe de lambdas:

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

Vous pouvez créer des tableaux de règles et les appliquer à votre guise.

IEnumerable<Rule> items = ...;

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

Si Champ et Les valeurs ne sont pas des nombres ou si le type dépend de la règle spécifique, vous pouvez utiliser objet au lieu de . décimal , et avec un peu de casting, vous pouvez tout faire fonctionner.

Ce n'est pas une conception finale; c'est juste pour vous donner quelques idées (par exemple, vous demanderiez probablement à la classe d'évaluer elle-même le délégué via une méthode Check () ou quelque chose du genre).

Vous pouvez récupérer le champ par réflexion. Et ensuite, implémentez les opérateurs en tant que méthodes et utilise la réflexion ou certains types de mappage enum-délégué pour appeler les opérateurs. Les opérateurs doivent avoir au moins 2 paramètres, la valeur d'entrée et la valeur que vous utilisez pour tester.

S'il est vrai que vous ne trouverez probablement pas de moyen élégant d'évaluer le code C # complet à la volée sans utiliser un code compilant de manière dynamique (ce qui n'est jamais joli), vous pouvez certainement faire évaluer vos règles rapidement. en utilisant le DLR (IronPython, IronRuby, etc.) ou une bibliothèque d’évaluateur d’expression qui analyse et exécute une syntaxe personnalisée. Il en existe un, Script.NET, qui fournit une syntaxe très similaire à C #.

Jetez un coup d’œil ici: Évaluation Expressions d'un runtime dans .NET (C #)

Si vous avez le temps / l’envie d’apprendre un peu Python, IronPython et le DLR résoudront tous vos problèmes: Extension de votre application avec IronPython

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top