eval (string) para o código C #
-
05-07-2019 - |
Pergunta
É possível avaliar o seguinte em C # em tempo de execução
I têm uma classe que contém 3 unidades de (Field
, Operator
, Value
)
rule.Field;
rule.Operator;
rule.Value;
Esta é a minha classe de regra ...
Agora eu tenho um loop
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 }
}
Eu só não sei a sintaxe, ou se é possível em C #, eu sei em JS é possível, mas isso não é uma linguagem compilada.
Atualizar
Basicamente eu quero uma maneira de eval(stringCode)
ou uma maneira melhor, mais suportado.
Solução
Eu não sou inteiramente certo o que você está dizendo. Você pode tentar esclarecer um pouco?
Você está querendo tomar uma expressão de cadeia e avaliá-lo em tempo de execução em C #? Se assim for a resposta é não. faz C # não suporta esses tipos de avaliação dinâmica.
Outras dicas
Não, C # não suporta nada parecido com isso diretamente.
As opções mais próximas são:
- Criar um programa completo válido C # e dinamicamente compilá-lo com
CSharpCodeProvider
. - Criar um expressão árvore , compilar e executá-lo
- Realizar a avaliação se (pode realmente ser mais fácil, dependendo de seus operadores etc)
Você teria que usar as bibliotecas CodeDOM ou criar uma árvore de expressão, compilá-lo e executá-lo. Eu acho que construir a árvore de expressão é a melhor opção.
Claro que você pode colocar em uma instrução switch do seu operador, que não é ruim, porque há um número limitado de operadores que você pode usar qualquer maneira.
Aqui está uma maneira de fazer isso com árvores de expressão (escrito em 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; }
}
A melhor projeto para você seria para a regra para aplicar a si (ou para um valor arbitrário) test
Ao fazer isso com instâncias Func você obterá o máximo de flexibilidade, assim:
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
}
}
}
então o seu teste específico seria algo como isto para verificação de tipo forte em tempo de compilação:
public Func<T,bool> FooEqualsX<T,V>(V x)
{
return t => EqualityComparer<V>.Default.Equals(t.Foo, x);
}
Para uma forma reflexiva
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 você queria ser realmente introspectivo e lidar com qualquer literal sem saber em tempo de compilação você poderia usar o CSharpCodeProvider para compilar uma função assumindo algo como:
public static bool Check(T t)
{
// your code inserted here
}
Este é, naturalmente, uma falha de segurança enorme para quem pode fornecer código para este deve ser totalmente confiável. Aqui é uma implementação de um pouco limitado para as suas necessidades específicas (sem verificação de sanidade em tudo)
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");
Para que isso funcione com tipos arbitrários você teria que fazer um pouco mais de reflexão para encontrar o alvo de montagem para o tipo de referência a ela nos parâmetros do compilador.
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 });
}
Todo o código acima é apenas uma prova de conceito, que carece de verificação de sanidade e tem problemas de desempenho graves.
Se você quiser ser ainda mais extravagante que você poderia usar Reflection.Emit com instâncias DynamicMethod para fazê-lo (usando operadores adequados, em vez das instâncias comparador padrão), mas isso exigiria manuseio complexo para tipos com operadores substituídos.
Ao fazer o seu código de verificação muito genérica que você pode incluir mais testes no futuro como você precisa. Essencialmente isolar a parte do seu código que se preocupa apenas com uma função de t -.> True / false a partir do código que fornece estas funções
Aviso : Eu sou o dono do projeto Eval Expression.NET
Esta biblioteca está perto de ser o equivalente JS Eval. Você quase pode avaliar e compilar toda a linguagem C #.
Aqui está um exemplo simples usando a sua pergunta, mas a biblioteca vai muito além deste cenário simples.
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
; declarações switch
que captam as adequadas diferentes "operadores"; o DLR ... eles são todas as maneiras que você poderia fazer isso; mas eles parecem soluções estranhas para mim.
Como apenas cerca de usar delegados?
Assumindo que o seu Field
e Value
são números, declarar algo como isto:
delegate bool MyOperationDelegate(decimal left, decimal right);
...
class Rule {
decimal Field;
decimal Value;
MyOperationDelegate Operator;
}
Agora você pode definir o seu 'regra' como, por exemplo, um grupo de lambdas:
Rule rule1 = new Rule;
rule1.Operation = (decimal l, decimal r) => { return l > r; };
rule1.Field = ...
Você pode fazer matrizes de regras e aplicá-los do jeito que você quiser.
IEnumerable<Rule> items = ...;
foreach(item in items)
{
if (item.Operator(item.Field, item.Value))
{ /* do work */ }
}
Se Field
e Values
não são números, ou o tipo depende da regra específica, você pode usar object
vez de decimal
, e com um pouco de vazamento você pode fazer tudo funcionar.
Isso não é um projeto final; é só para lhe dar algumas idéias (por exemplo, você provavelmente teria a classe avaliar o delegado por conta própria através de um método Check()
ou algo assim).
Você pode recuperar o campo de reflexão. E, em seguida, implementar os operadores como métodos e usos reflexão ou alguns tipos de mapeamento enum-delegado para chamar os operadores. Os operadores devem ter pelo menos 2 parâmetros, o valor de entrada eo valor que você está usando para teste contra com.
Embora seja verdade que você provavelmente não vai encontrar uma maneira elegante para avaliar código completo C # sobre a voar sem o uso de compilar dinamicamente o código (que nunca é muito), você pode quase certamente se suas regras avaliadas na ordem curta utilizando o DLR (IronPython, IronRuby, etc) ou uma biblioteca avaliador de expressão que analisa e executa uma sintaxe personalizado. Há um, Script.NET, que fornece uma sintaxe muito semelhante ao C #.
Dê uma olhada aqui: Avaliando expressões um Runtime em .NET (C #)
Se você tem o tempo / inclinação para aprender um pouco Python, então IronPython eo DLR vai resolver todos os seus problemas: Estendendo seu aplicativo com IronPython