eval(строка) для кода C #
-
05-07-2019 - |
Вопрос
Можно ли оценить следующее в 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)
или лучший, более поддерживаемый способ.
Решение
Я не совсем понимаю, о чем вы говорите.Не могли бы вы попробовать немного прояснить это?
Вы хотите взять строковое выражение и оценить его во время выполнения на C #?Если это так, то ответ - нет.C # не поддерживает такие типы динамической оценки.
Другие советы
Нет, C # напрямую ничего подобного не поддерживает.
Наиболее близкими вариантами являются:
- Создайте полноценную программу на C # и динамически скомпилируйте ее с помощью
CSharpCodeProvider
. - Построить дерево выражений, скомпилируйте и выполните его
- Выполните оценку самостоятельно (на самом деле это может быть проще всего, в зависимости от ваших операторов и т.д.)
Вам нужно будет либо использовать библиотеки 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; }
}
Лучшим вариантом для вас было бы, если бы ваше правило применяло сам тест (или к произвольному значению)
Делая это с экземплярами 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 });
}
Весь приведенный выше код является просто доказательством концепции, в нем отсутствует проверка на работоспособность и есть серьезные проблемы с производительностью.
Если бы вы хотели быть еще более причудливым, вы могли бы использовать Рефлексию.Для этого используйте экземпляры DynamicMethod (используя соответствующие операторы, а не экземпляры компаратора по умолчанию), но это потребовало бы сложной обработки для типов с переопределенными операторами.
Сделав свой проверочный код очень универсальным, вы можете в будущем включать дополнительные тесты по мере необходимости.По сути, изолируйте часть вашего кода, которая заботится только о функции из t -> true / false, от кода, который предоставляет эти функции.
Отказ от ответственности:Я владелец проекта Оценка 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);
CSharpCodeProvider
; switch
операторы, которые выбирают соответствующие различные "операторы";ДЛР...это все способы, которыми вы могли бы это сделать;но мне они кажутся странными решениями.
Как насчет простого использования делегатов?
Предполагая, что ваш 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 параметра: входное значение и значение, которое вы используете для проверки.
Хотя верно, что вы, вероятно, не найдете элегантного способа оценить полный код C # "на лету" без использования динамически компилируемого кода (что никогда не бывает красивым), вы почти наверняка можете быстро оценить свои правила, используя либо DLR (IronPython, IronRuby и т.д.), Либо библиотеку expression evaluator, которая анализирует и выполняет пользовательский синтаксис.Есть один, Script.NET, который предоставляет синтаксис, очень похожий на C #.
Взгляните сюда:Вычисление выражений во время выполнения в .NET(C#)
Если у вас есть время / желание немного выучить Python, то IronPython и DLR решат все ваши проблемы:Расширение вашего приложения с помощью IronPython