Eval (строка) в C # код - PullRequest
       12

Eval (строка) в C # код

3 голосов
/ 30 июля 2009

Можно ли оценить следующее в C # во время выполнения

У меня есть класс, который содержит 3 свойства (Field, Operator, Value)

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

это мой класс правил ...

Теперь у меня есть петля

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

Я просто не знаю синтаксис или, если это возможно в C #, я знаю, что в JS это возможно, но это не скомпилированный язык.

Обновление

По сути, я хочу способ eval(stringCode) или лучший, более поддерживаемый способ.

Ответы [ 8 ]

9 голосов
/ 30 июля 2009

Нет, C # не поддерживает ничего подобного напрямую.

Ближайшие варианты:

  • Создайте полную действительную программу на C # и динамически скомпилируйте ее с помощью CSharpCodeProvider.
  • Построить дерево выражений , скомпилировать и выполнить его
  • Выполните оценку самостоятельно (это может быть проще всего, в зависимости от ваших операторов и т. Д.)
2 голосов
/ 10 февраля 2016

Отказ от ответственности : я владелец проекта Eval Expression.NET

Эта библиотека близка к тому, чтобы быть эквивалентом JS Eval. Вы можете почти оценить и скомпилировать весь язык C #.

Вот простой пример использования вашего вопроса, но библиотека выходит далеко за рамки этого простого сценария.

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);
2 голосов
/ 30 июля 2009

Лучшим дизайном для вас было бы правило применять сам тест (или к произвольному значению)

Делая это с экземплярами Func, вы получите максимальную гибкость, например:

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

тогда ваш конкретный тест будет выглядеть примерно так для строгой проверки типов во время компиляции:

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

Для отражающей формы

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

Если вы хотите быть действительно интроспективным и обрабатывать любой литерал, не зная во время компиляции, вы можете использовать CSharpCodeProvider для компиляции функции, предполагая что-то вроде:

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

Это, конечно, огромная дыра в безопасности, поэтому тот, кто может предоставить код для этого, должен быть полностью доверенным. Вот несколько ограниченная реализация для ваших конкретных потребностей (вообще без проверки работоспособности)

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

Чтобы это работало с произвольными типами, вам нужно было бы немного больше подумать, чтобы найти целевую сборку для типа, чтобы ссылаться на нее в параметрах компилятора.

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

Весь приведенный выше код является просто подтверждением концепции, в нем отсутствует проверка работоспособности и имеются серьезные проблемы с производительностью.

Если вы хотите быть еще более искушенным, вы можете использовать Reflection.Emit с экземплярами DynamicMethod, чтобы сделать это (используя правильные операторы, а не экземпляры компаратора по умолчанию), но это потребует сложной обработки для типов с переопределенными операторами.

Делая ваш код проверки очень универсальным, вы можете включать в будущем больше тестов, сколько вам нужно. По сути, изолируйте часть вашего кода, которая заботится только о функции, из t -> true / false из кода, который предоставляет эти функции.

2 голосов
/ 30 июля 2009

Вам придется либо использовать библиотеки CodeDOM, либо создать дерево выражений, скомпилировать его и выполнить. Я думаю, что построение дерева выражений - лучший вариант.

Конечно, вы можете добавить оператор switch к своему оператору, что неплохо, потому что в любом случае вы можете использовать ограниченное количество операторов.

Вот способ сделать это с деревьями выражений (написано в 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; }
}
1 голос
/ 30 июля 2009

CSharpCodeProvider; switch операторы, которые выбирают правильных разных «операторов»; DLR ... это все способы, которыми вы могли бы сделать это; но они кажутся мне странными решениями.

Как насчет использования делегатов?

Предполагая, что Field и Value являются числами, объявите что-то вроде этого:

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

Теперь вы можете определить свое «правило», например, как набор лямбд:

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

Вы можете создавать массивы правил и применять их по своему усмотрению.

IEnumerable<Rule> items = ...;

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

Если Field и Values не являются числами или тип зависит от конкретного правила, вы можете использовать object вместо decimal, и с небольшим кастом вы можете заставить все это работать.

Это не окончательный дизайн; это просто для того, чтобы дать вам некоторые идеи (например, вы, вероятно, попросите класс самостоятельно оценить делегата с помощью метода Check() или чего-то еще).

1 голос
/ 30 июля 2009

Я не совсем уверен, что вы говорите. Можете ли вы попытаться объяснить это немного?

Вы хотите взять строковое выражение и оценить его во время выполнения в C #? Если так, то ответ - нет. C # не поддерживает такие типы динамической оценки.

0 голосов
/ 30 июля 2009

Хотя верно, что вы, вероятно, не найдете элегантного способа оценки полного кода C # на лету без использования динамически компилируемого кода (что никогда не бывает красиво), вы почти наверняка сможете оценить свои правила в короткие сроки используя либо DLR (IronPython, IronRuby и т. д.), либо библиотеку оценщика выражений, которая анализирует и выполняет пользовательский синтаксис. Существует один, Script.NET, который обеспечивает синтаксис, очень похожий на C #.

Посмотрите здесь: Оценка выражений во время выполнения в .NET (C #)

Если у вас есть время / желание немного изучить Python, то IronPython и DLR решат все ваши проблемы: Расширение вашего приложения с помощью IronPython

0 голосов
/ 30 июля 2009

Вы можете получить поле по отражению. А затем реализуйте операторы как методы и для отражения операторов используйте отражение или некоторые типы отображения перечисления-делегата. Операторы должны иметь как минимум 2 параметра, входное значение и значение, которое вы используете для проверки.

...