eval(字符串) 到 C# 代码

发布于 2024-07-29 03:21:56 字数 606 浏览 5 评论 0原文

是否可以在运行时在 C# 中评估以下内容

我有一个包含 3 个属性的类(FieldOperatorValue),

 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)的方法或更好的更受支持的方法。

Is it possible to evaluate the following in C# at runtime

I have a class that contains 3 properties (Field,Operator,Value)

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

this is my rule class...

Now I have a 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 }
   }

I just don't know the syntax, or if its possible in C#, I know in JS its possible but that's not a compiled language.

Update

Essentially I want a way to eval(stringCode) or a better more supported way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

数理化全能战士 2024-08-05 03:21:56

不,C# 不直接支持这样的东西。

最接近的选项是:

  • 创建完整有效的 C# 程序并使用 CSharpCodeProvider
  • 构建表达式树,编译并执行它
  • 自己执行评估(这实际上可能是最简单的,具体取决于您的运营商等)

No, C# doesn't support anything like this directly.

The closest options are:

  • Create a full valid C# program and dynamically compile it with CSharpCodeProvider.
  • Build an expression tree, compile and execute it
  • Perform the evaluation yourself (this may actually be easiest, depending on your operators etc)
不必在意 2024-08-05 03:21:56

免责声明:我是项目 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);

编辑回答评论:

专有库可以做简单的评估吗? 不用了,谢谢

这个库不仅支持简单的计算,而且支持几乎所有的 C# 语言。 允许您动态添加方法,使用 async、linq、loop 等,这不仅仅是“进行简单的评估”

Jon Skeet 提供的最接近的选项解决方案很棒,但肯定需要几天的开发和测试才能支持所有情况,取决于运营商。 当然,这个库可以帮助一些开发人员,但在其他一些情况下,比如你的情况,没有它也可以完成。

Disclaimer: I'm the owner of the project Eval Expression.NET

This library is close to being the JS Eval equivalent. You can almost evaluate and compile all the C# language.

Here is a simple example using your question, but the library goes way beyond this simple scenario.

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

EDIT Answer comment:

Proprietary library to do simple evaluation? No, thanks

This library does not support only simple evaluation but almost all the C# languages. Allowing you to add dynamically a method, use async, linq, loop, etc., which is more than "to do simple evaluation"

The closest options solution provided by Jon Skeet are great but will surely take several days of development and testing to support all cases, depending on the operators. Surely this library helps some developers, but in some other scenarios, like yours, it could be done without it.

§对你不离不弃 2024-08-05 03:21:56

我不完全确定你在说什么。 你能试着澄清一下吗?

您是否想要获取一个字符串表达式并在运行时在 C# 中对其求值? 如果是这样,答案是否定的。 C# 不支持此类动态求值。

I'm not entirely sure what you are saying. Can you try clarifying it a bit?

Are you wanting to to take a string expression and evaluate it at runtime in C#? If so the answer is no. C# does not support such types of dynamic evaluation.

咋地 2024-08-05 03:21:56

您必须使用 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; }
}

You'd have to either use the CodeDOM libraries or create an Expression tree, compile it, and execute it. I think building up the expression tree is the best option.

Of course you could put in a switch statement on your operator, which is not bad because there is a limited number of operators you could use anyways.

Here's a way to do this with expression trees (written in 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; }
}
一个人的夜不怕黑 2024-08-05 03:21:56

对您来说更好的设计是让您的规则应用测试本身(或任意值)

通过使用 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 -> 隔离开来。 真/假来自提供这些函数的代码。

A better design for you would be for your rule to apply the test itself (or to an arbitrary value)

By doing this with Func instances you will get the most flexibility, like so:

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

then your specific test would be something like this for strong type checking at compile time:

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

For a reflective form

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

If you wanted to be really introspective and handle any literal without knowing at compile time you could use the CSharpCodeProvider to compile a function assuming something like:

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

This is of course a massive security hole so whoever can supply code for this must be fully trusted. Here is a somewhat limited implementation for your specific needs (no sanity checking at all)

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

For this to work with arbitrary types you would have to do a bit more reflection to find the target assembly for the type to reference it in the compiler parameters.

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

All the above code is simply a proof of concept, it lacks sanity checking and has serious performance issues.

If you wanted to be even fancier you could use Reflection.Emit with DynamicMethod instances to do it (using proper operators rather than the default comparer instances) but this would require complex handling for types with overridden operators.

By making your check code highly generic you may include more tests in future as you need to. Essentially isolate the part of your code that cares only about a function from t -> true/false from the code that supplies these functions.

梦里寻她 2024-08-05 03:21:56

CSharpCodeProvider; switch 语句选择适当的不同“运算符”; DLR...它们都是您可以做到这一点的方法; 但它们对我来说似乎很奇怪。

只使用代表怎么样?

假设您的 FieldValue 是数字,请声明如下内容:

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

现在您可以将您的“规则”定义为例如一堆 lambda:

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

如果FieldValues不是数字,或者类型取决于具体规则,则可以使用object而不是decimal,并且通过一点点转换就可以使其全部工作。

这不是最终设计; 它只是为了给您一些想法(例如,您可能会让该类通过 Check() 方法或其他方法自行评估委托)。

CSharpCodeProvider; switch statements that pick the proper different "operators"; the DLR... they are all ways you could do this; but they seem weird solutions to me.

How about just using delegates?

Assuming your Field and Value are numbers, declare something like this:

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

Now you can define your 'rule' as, for example, a bunch of lambdas:

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

You can make arrays of rules and apply them whichever way you wish.

IEnumerable<Rule> items = ...;

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

If Field and Values are not numbers, or the type depends on the specific rule, you can use object instead of decimal, and with a little bit of casting you can make it all work.

That's not a final design; it's just to give you some ideas (for example, you would likely have the class evaluate the delegate on its own via a Check() method or something).

一笔一画续写前缘 2024-08-05 03:21:56

虽然您可能无法找到一种优雅的方法来在不使用动态编译代码的情况下动态评估完整的 C# 代码(这从来都不是很漂亮),但您几乎肯定可以使用以下任一方法快速评估您的规则: DLR(IronPython、IronRuby 等)或解析并执行自定义语法的表达式计算器库。 Script.NET 提供了与 C# 非常相似的语法。

看一下这里:在运行时评估表达式在 .NET(C#)

如果您有时间/愿意学习一点 Python,那么 IronPython 和 DLR 将解决您的所有问题:
使用 IronPython 扩展您的应用

While it is true that you probably won't find an elegant way to evaluate full C# code on the fly without the use of dynamically compiling code (which is never pretty), you can almost certainly get your rules evaluated in short order using either the DLR (IronPython, IronRuby, etc) or an expression evaluator library that parses and executes a custom syntax. There is one, Script.NET, that provides a very similar syntax to C#.

Take a look here:Evaluating Expressions a Runtime in .NET(C#)

If you have the time / inclination to learn a little Python, then IronPython and the DLR will solve all your issues:
Extending your App with IronPython

洋洋洒洒 2024-08-05 03:21:56

您可以通过反射来检索该字段。 然后将运算符实现为方法,并使用反射或某些类型的枚举委托映射来调用运算符。 运算符应至少有 2 个参数,即输入值和用于测试的值。

You can retrieve the field by reflection. And then implement the operators as methods and uses reflection or some types of enum-delegate mapping to call the operators. The operators should have at least 2 parameters, the input value and the value you are using to test against with.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文