2009-07-30 2 views
3

# Можно ли оценить следующее C# во время выполненияEval (строка) для 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 его возможным, но это не скомпилированный язык.

Update

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

+0

Тогда решение с делегатами, похоже, очень близко к тому, что вам нужно; если stringCode не является действительно динамичным (контент, предоставляемый пользователем, «на лету»). Затем вам понадобится что-то через DLR (который будет готов в .NET 4.0, и я не знаю достаточно о том, чтобы помочь) –

+0

, пока ваш конкретный случай остается постоянным, и вы можете его достичь. Если вам нужна возможность делать * произвольную * логику, а фиксированную форму, описанную, тогда у вас возникнут проблемы. По сути, если набор всех входов в ваш «код» известен заранее, и никаких побочных эффектов нет, кроме возврата одного значения (опять же, когда тип известен во время компиляции (даже если это только «объект») то это возможно. Все, что находится за пределами этого, и вы находитесь за пределами того, что может сделать что-то вроде «eval». – ShuggyCoUk

+0

А я просто видел ваше обновление. Нет, вы не можете получить полные возможности того, что eval делает в чем-то вроде javascript. стремление получить все больше и больше функциональности станет экспоненциально сложнее, так как вам нужно больше и больше аспектов, которые бы сработали, если бы вы могли просто вставить код в класс во время компиляции. В конце концов вы бы нанесли что-то вроде лямбда, где локальная переменная был повышен до переменной экземпляра, и интроспективное состояние выполнения больше не было похоже на лексическую структуру времени компиляции, чтобы позволить этой концепции работать дальше. – ShuggyCoUk

ответ

1

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

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

+0

Да, это именно то, что я пытаюсь сделать. –

+0

@JL, к сожалению, этот тип оценки не поддерживается в C# (даже в 4.0). – JaredPar

+0

как можно сравнить с помощью динамического оператора? –

2

Вам нужно будет либо использовать библиотеки 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; } 
} 
8

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

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

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

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

1

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() или что-то в этом роде).

2

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

Делая это с экземплярами 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 из кода, который предоставляет эти функции.

0

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

Посмотрите здесь: Evaluating Expressions a Runtime in .NET(C#)

Если у вас есть время/наклонение, чтобы узнать немного Python, то IronPython и DLR будет решать все ваши вопросы: Extending your App with IronPython

2

Отказ от ответственности: Я м владелец проекта 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); 
Смежные вопросы