【问题标题】:How can I evaluate C# code dynamically?如何动态评估 C# 代码?
【发布时间】:2010-09-05 12:00:16
【问题描述】:

我可以通过eval("something()"); 在 JavaScript 中动态执行代码。有没有办法让我在 C# 中做同样的事情?

我想要做的一个例子是:我有一个整数变量(比如i),并且我有多个属性名称:“Property1”、“Property2”、“Property3”等。 现在,我想根据i 的值对“Propertyi”属性执行一些操作。

这对于 Javascript 来说非常简单。有没有办法用 C# 做到这一点?

【问题讨论】:

  • c#调用ironpython的eval。我在 c# 4.0 中尝试过。没有 c# 2.0 的经验
  • @Peter Long,我在哪里可以找到有关 IronPython 的 eval 的文档?
  • @AdhipGupta 我知道这个问答已经过时了,但我刚刚发布了一个video playlist,这听起来很像 Davide Icardi 的回答中给出的描述。它截然不同,可能值得一试。

标签: c# reflection properties c#-2.0


【解决方案1】:

使用 Roslyn 脚本 API(更多 samples here):

// add NuGet package 'Microsoft.CodeAnalysis.Scripting'
using Microsoft.CodeAnalysis.CSharp.Scripting;

await CSharpScript.EvaluateAsync("System.Math.Pow(2, 4)") // returns 16

你也可以运行任何一段代码:

var script = await CSharpScript.RunAsync(@"
                class MyClass
                { 
                    public void Print() => System.Console.WriteLine(1);
                }")

并引用之前运行中生成的代码:

await script.ContinueWithAsync("new MyClass().Print();");

【讨论】:

    【解决方案2】:

    免责声明:此答案写于 2008 年。从那时起,情况发生了翻天覆地的变化。

    查看此页面上的其他答案,尤其是详细说明 Microsoft.CodeAnalysis.CSharp.Scripting 的答案。

    其余答案将与最初发布的相同,但不再准确。


    很遗憾,C# 不是这样的动态语言。

    但是,您可以做的是创建一个包含类和所有内容的 C# 源代码文件,然后通过 C# 的 CodeDom 提供程序运行它并将其编译成程序集,然后执行它。

    MSDN 上的这个论坛帖子包含一个答案,页面下方有一些示例代码:
    create a anonymous method from a string?

    我很难说这是一个很好的解决方案,但无论如何都是可能的。

    您希望该字符串中有什么样的代码?如果它是有效代码的一小部分,例如只是数学表达式,则可能存在其他替代方案。


    编辑:嗯,这教会我先彻底阅读问题。是的,反射可以在这里给你一些帮助。

    如果你用 ; 分割字符串首先,要获取单个属性,您可以使用以下代码为类的特定属性获取 PropertyInfo 对象,然后使用该对象来操作特定对象。

    String propName = "Text";
    PropertyInfo pi = someObject.GetType().GetProperty(propName);
    pi.SetValue(someObject, "New Value", new Object[0]);
    

    链接:PropertyInfo.SetValue Method

    【讨论】:

    • 如果 GetProperty 必须是一个带有 x,y 参数的函数怎么办?,意思是 Text(1,2) ?
    • @user1735921 然后你需要使用GetMethod(methodName),解析参数值,并使用反射调用方法。
    • typeof(ObjectType) 类似于 someObject.GetType()
    • 这个答案不再正确。可以使用Microsoft.CodeAnalysis.Scripting 包中的CSharpScript 类来评估和执行脚本。人们正在使用此作为重复来关闭类似的问题,这会导致混淆
    • 我同意这个答案不再正确。理想情况下,已接受的答案应移至此处的其他答案之一。虽然我不同意最后的建议,因为您没有链接到重复的answers,而是重复questions,有时它不是公认的答案那个被关闭为副本。
    【解决方案3】:

    不是真的。您可以使用反射来实现您想要的,但它不会像在 Javascript 中那么简单。例如,如果你想将一个对象的私有字段设置为某个东西,你可以使用这个函数:

    protected static void SetField(object o, string fieldName, object value)
    {
       FieldInfo field = o.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
       field.SetValue(o, value);
    }
    

    【讨论】:

      【解决方案4】:

      这是一个c#下的eval函数。我用它从字符串转换匿名函数(Lambda 表达式)。 来源:http://www.codeproject.com/KB/cs/evalcscode.aspx

      public static object Eval(string sCSCode) {
      
        CSharpCodeProvider c = new CSharpCodeProvider();
        ICodeCompiler icc = c.CreateCompiler();
        CompilerParameters cp = new CompilerParameters();
      
        cp.ReferencedAssemblies.Add("system.dll");
        cp.ReferencedAssemblies.Add("system.xml.dll");
        cp.ReferencedAssemblies.Add("system.data.dll");
        cp.ReferencedAssemblies.Add("system.windows.forms.dll");
        cp.ReferencedAssemblies.Add("system.drawing.dll");
      
        cp.CompilerOptions = "/t:library";
        cp.GenerateInMemory = true;
      
        StringBuilder sb = new StringBuilder("");
        sb.Append("using System;\n" );
        sb.Append("using System.Xml;\n");
        sb.Append("using System.Data;\n");
        sb.Append("using System.Data.SqlClient;\n");
        sb.Append("using System.Windows.Forms;\n");
        sb.Append("using System.Drawing;\n");
      
        sb.Append("namespace CSCodeEvaler{ \n");
        sb.Append("public class CSCodeEvaler{ \n");
        sb.Append("public object EvalCode(){\n");
        sb.Append("return "+sCSCode+"; \n");
        sb.Append("} \n");
        sb.Append("} \n");
        sb.Append("}\n");
      
        CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
        if( cr.Errors.Count > 0 ){
            MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText, 
               "Error evaluating cs code", MessageBoxButtons.OK, 
               MessageBoxIcon.Error );
            return null;
        }
      
        System.Reflection.Assembly a = cr.CompiledAssembly;
        object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");
      
        Type t = o.GetType();
        MethodInfo mi = t.GetMethod("EvalCode");
      
        object s = mi.Invoke(o, null);
        return s;
      
      }
      

      【讨论】:

      • @sehe 哎呀,我纠正了错字(Lambada => Lambda)。我不知道这首歌叫 Lambada 所以这首歌是无意的。 ;)
      • 我不明白为什么这个答案得到的投票更少。这非常有用。
      【解决方案5】:

      我编写了一个开源项目Dynamic Expresso,它可以将使用 C# 语法编写的文本表达式转换为委托(或表达式树)。表达式被解析并转换为Expression Trees,而不使用编译或反射。

      你可以这样写:

      var interpreter = new Interpreter();
      var result = interpreter.Eval("8 / 2 + 2");
      

      var interpreter = new Interpreter()
                            .SetVariable("service", new ServiceExample());
      
      string expression = "x > 4 ? service.SomeMethod() : service.AnotherMethod()";
      
      Lambda parsedExpression = interpreter.Parse(expression, 
                                new Parameter("x", typeof(int)));
      
      parsedExpression.Invoke(5);
      

      我的作品基于 Scott Gu 的文章http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

      【讨论】:

        【解决方案6】:

        所有这些肯定会奏效。就个人而言,对于那个特定的问题,我可能会采取一些不同的方法。也许是这样的:

        class MyClass {
          public Point point1, point2, point3;
        
          private Point[] points;
        
          public MyClass() {
            //...
            this.points = new Point[] {point1, point2, point3};
          }
        
          public void DoSomethingWith(int i) {
            Point target = this.points[i+1];
            // do stuff to target
          }
        }
        

        当使用这样的模式时,您必须小心您的数据是按引用而不是按值存储的。换句话说,不要对原语执行此操作。您必须使用他们臃肿的类对应物。

        我意识到这不完全是问题,但这个问题已经得到了很好的回答,我认为另一种方法可能会有所帮助。

        【讨论】:

          【解决方案7】:

          如果您绝对要执行 C# 语句,我现在不这样做,但是您已经可以在 C# 2.0 中执行 Javascript 语句。开源库Jint 能够做到这一点。它是 .NET 的 Javascript 解释器。传递一个 Javascript 程序,它将在您的应用程序中运行。您甚至可以将 C# 对象作为参数传递并对其进行自动化处理。

          此外,如果您只想评估属性的表达式,请尝试NCalc

          【讨论】:

            【解决方案8】:

            您可以使用反射来获取属性并调用它。像这样的:

            object result = theObject.GetType().GetProperty("Property" + i).GetValue(theObject, null);
            

            也就是说,假设具有该属性的对象称为“theObject”:)

            【讨论】:

              【解决方案9】:

              您也可以实现一个网络浏览器,然后加载一个包含 javascript 的 html 文件。

              然后你在这个浏览器上使用document.InvokeScript 方法。 eval 函数的返回值可以被捕获并转换成你需要的一切。

              我在几个项目中都这样做过,效果很好。

              希望对你有帮助

              【讨论】:

                【解决方案10】:

                使用反射在运行时针对对象解析和评估数据绑定表达式。

                DataBinder.Eval Method

                【讨论】:

                  【解决方案11】:

                  你可以用原型函数来做到这一点:

                  void something(int i, string P1) {
                      something(i, P1, String.Empty);
                  }
                  
                  void something(int i, string P1, string P2) {
                      something(i, P1, P2, String.Empty);
                  }
                  
                  void something(int i, string P1, string P2, string P3) {
                      something(i, P1, P2, P3, String.Empty);
                  }
                  

                  等等……

                  【讨论】:

                    【解决方案12】:

                    我编写了一个包SharpByte.Dynamic,以简化动态编译和执行代码的任务。可以使用扩展方法在任何上下文对象上调用代码,详见here

                    例如,

                    someObject.Evaluate<int>("6 / {{{0}}}", 3))
                    

                    返回 3;

                    someObject.Evaluate("this.ToString()"))
                    

                    返回上下文对象的字符串表示;

                    someObject.Execute(@
                    "Console.WriteLine(""Hello, world!"");
                    Console.WriteLine(""This demonstrates running a simple script"");
                    ");
                    

                    将这些语句作为脚本等运行。

                    可以使用工厂方法轻松获取可执行文件,如示例here 所示——您只需要源代码和任何预期命名参数的列表(令牌使用三括号表示法嵌入,例如 { {{0}}},以避免与 string.Format() 以及类似 Handlebars 的语法发生冲突):

                    IExecutable executable = ExecutableFactory.Default.GetExecutable(executableType, sourceCode, parameterNames, addedNamespaces);
                    

                    每个可执行对象(脚本或表达式)都是线程安全的,可以存储和重用,支持从脚本中记录日志,存储时间信息和遇到的最后一个异常等。还有一个 Copy() 方法编译在每个都允许创建廉价副本,即使用从脚本或表达式编译的可执行对象作为创建其他对象的模板。

                    执行已编译的脚本或语句的开销相对较低,在普通硬件上不到一微秒,并且已编译的脚本和表达式被缓存以供重复使用。

                    【讨论】:

                      【解决方案13】:

                      我试图通过名称获取结构(类)成员的值。该结构不是动态的。在我最终得到答案之前,所有答案都不起作用:

                      public static object GetPropertyValue(object instance, string memberName)
                      {
                          return instance.GetType().GetField(memberName).GetValue(instance);
                      }
                      

                      此方法将通过成员的名称返回成员的值。它适用于常规结构(类)。

                      【讨论】:

                        【解决方案14】:

                        您可以查看Heleonix.Reflection 库。它提供了动态获取/设置/调用成员的方法,包括嵌套成员,或者如果明确定义了成员,您可以创建比反射更快的getter/setter(lambda编译成委托):

                        var success = Reflector.Set(instance, null, $"Property{i}", value);
                        

                        或者,如果属性的数量不是无穷无尽的,您可以生成 setter 并对其进行 chache(setter 更快,因为它们是编译的委托):

                        var setter = Reflector.CreateSetter<object, object>($"Property{i}", typeof(type which contains "Property"+i));
                        setter(instance, value);
                        

                        Setter 可以是 Action&lt;object, object&gt; 类型,但实例在运行时可能不同,因此您可以创建 setter 列表。

                        【讨论】:

                          【解决方案15】:

                          不幸的是,C# 没有任何本地工具可以完全按照您的要求进行操作。

                          但是,我的 C# eval 程序确实允许评估 C# 代码。它提供了在运行时评估 C# 代码并支持许多 C# 语句。事实上,此代码可用于任何 .NET 项目,但仅限于使用 C# 语法。请访问我的网站http://csharp-eval.com,了解更多详情。

                          【讨论】:

                          【解决方案16】:

                          正确的答案是您需要缓存所有结果以保持低内存使用率。

                          示例如下所示

                          TypeOf(Evaluate)
                          {
                          "1+1":2;
                          "1+2":3;
                          "1+3":5;
                          ....
                          "2-5":-3;
                          "0+0":1
                          } 
                          

                          并将其添加到列表中

                          List<string> results = new List<string>();
                          for() results.Add(result);
                          

                          保存id并在代码中使用

                          希望对你有帮助

                          【讨论】:

                          • 有人将评估与查找混淆了。如果你知道所有可能的程序(我认为那是至少 NP-Hard)......并且你有一台超级机器来预编译所有可能的结果...... 并且没有副作用/外部输入...是的,这个想法理论上有效。不过,代码是一个很大的语法错误
                          猜你喜欢
                          • 2011-07-29
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-01-25
                          相关资源
                          最近更新 更多