【问题标题】:eval(string) to C# codeeval(string) 到 C# 代码
【发布时间】:2009-07-30 16:41:34
【问题描述】:

是否可以在运行时在 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) 或更好更受支持的方式。

【问题讨论】:

  • 那么代表的解决方案似乎非常接近您的需要;除非 stringCode 是真正动态的(用户提供的内容,即时)。然后,您将需要通过 DLR 获得一些东西(这将在 .NET 4.0 中推出,我不太了解如何提供帮助)
  • 只要您的具体情况保持不变,您就可以实现它。如果您需要执行 arbitrary 逻辑而不是所描述的固定形式的能力,那么您将遇到问题。基本上只要事先知道“代码”的所有输入的集合,并且除了返回单个值之外没有副作用(同样在编译时已知类型(即使只是作为“对象”)那么这是可能的。除此之外的任何事情,你都超出了“eval”之类的东西可以做的事情。
  • 啊我刚看到你的更新。不,您无法获得 eval 在 javascript 之类的东西中所做的那种事情的全部功能。如果您可以在编译时将代码插入到类中,那么您想要越来越多的方面会起作用,因此获得越来越多的功能的努力将成倍增加。最终,您会遇到类似于 lambda 的情况,其中局部变量已被提升为实例变量,并且自省的运行时状态不再与编译时词法结构足够相似,无法让该概念进一步发挥作用。
  • 不过,您可以轻松地完成很多 c# 1.0 级别的工作。

标签: c# runtime evaluation


【解决方案1】:

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

最接近的选项是:

  • 创建一个完整有效的 C# 程序并使用 CSharpCodeProvider 动态编译它。
  • 构建一个expression tree,编译并执行它
  • 自己执行评估(这实际上可能最简单,取决于您的运营商等)

【讨论】:

    【解决方案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);
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 是的,这正是我想要做的。
      • @JL 不幸的是,C# 不支持这种类型的评估(即使在 4.0 中)。
      • 那么如何使用动态运算符进行比较?
      • JL:“使用动态运算符进行比较”是什么意思?
      • 嗯第一篇文章我看到 Jon Sheet 的回复不是公认的答案:)
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        通过对 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 与提供这些函数的代码隔离开来。

        【讨论】:

          【解决方案6】:

          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() 方法或其他方法自行评估委托)。

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              确实,如果不使用动态编译代码(从不漂亮),您可能无法找到一种优雅的方式来动态评估完整的 C# 代码,但您几乎可以肯定地在短时间内评估您的规则使用 DLR(IronPython、IronRuby 等)或解析和执行自定义语法的表达式评估器库。 Script.NET 提供了与 C# 非常相似的语法。

              看这里:Evaluating Expressions a Runtime in .NET(C#)

              如果您有时间/有兴趣学习一点 Python,那么 IronPython 和 DLR 将解决您的所有问题: Extending your App with IronPython

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-07-29
                • 1970-01-01
                • 2013-07-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多