是否可以在运行时评估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)
或更好的支持方式。
答案 0 :(得分:9)
不,C#不直接支持这样的事情。
最接近的选项是:
CSharpCodeProvider
动态编译它。答案 1 :(得分: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; }
}
答案 2 :(得分: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 - &gt;提供这些函数的代码中的true / false。
答案 3 :(得分: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);
答案 4 :(得分:1)
我不完全确定你在说什么。你能尝试澄清一下吗?
您是否想要在C#中运行字符串表达式并在运行时对其进行评估?如果是这样,答案是否定的。 C#不支持此类动态评估。
答案 5 :(得分:1)
CSharpCodeProvider
; switch
语句选择适当的不同“运算符”; DLR ......他们都是你能做到这一点的方式;但它们对我来说似乎很奇怪。
如何使用代表?
假设您的Field
和Value
是数字,请声明如下:
delegate bool MyOperationDelegate(decimal left, decimal right);
...
class Rule {
decimal Field;
decimal Value;
MyOperationDelegate Operator;
}
现在你可以将你的'规则'定义为,例如,一堆lambdas:
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()
方法或其他东西自己评估委托。)
答案 6 :(得分:0)
您可以通过反射检索字段。然后将运算符实现为方法,并使用反射或某些类型的枚举委托映射来调用运算符。运算符应至少有2个参数,输入值和用于测试的值。
答案 7 :(得分:0)
虽然你可能找不到一种优雅的方法来动态评估完整的C#代码而不使用动态编译代码(这种代码永远不会很漂亮),但你几乎可以肯定地在短时间内评估你的规则使用DLR(IronPython,IronRuby等)或解析并执行自定义语法的表达式求值程序库。有一个Script.NET,它提供了与C#非常相似的语法。
看看这里:Evaluating Expressions a Runtime in .NET(C#)
如果您有时间/倾向学习一点Python,那么IronPython和DLR将解决您的所有问题: Extending your App with IronPython