2

我想用 c# 编写一段代码,它能够编译和执行在 windows 窗体中作为字符串(有效的 c# 代码)输入的用户定义的公式。有没有一种简单而优雅的方法来做到这一点?

例如,请参见下面的方法:

public double UserDefinedFormula(double x, double y, string str)  
{
    double z;

    // BEGIN: Formula code provided by user as string

    if (str == "sum")
        z = x + y;
    else
        z = Math.Sqrt(x + y + 5);

    // END: Formula code provided by user as string

    return z; 
}

用户输入的公式字符串可以包含任何内容,只要它是有效的 c# 代码即可。在上面的示例中,用户在参数表单的文本区域中输入以下字符串:

if (str == "sum")
    z = x + y;
else
    z = Math.Sqrt(x + y + 5);
4

3 回答 3

3

您可以使用此代码段:

[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IXyFunction<TInput, TOutput>
{
    TOutput Run(TInput x, TInput y);
}

public static class CodeProvider
{
    public static string LastError { get; private set; }
    private static int counter;

    public static IXyFunction<TInput, TOutput> Generate<TInput, TOutput>(string code)
    {
        return Generate<TInput, TOutput>(code, null);
    }

    public static IXyFunction<TInput, TOutput> Generate<TInput, TOutput>(string code, string[] assemblies)
    {
        if (String.IsNullOrEmpty(code))
            throw new ArgumentNullException("code");

        const string ERROR = "Error(s) while compiling";
        string className = "_generated_" + counter++;
        string typeInName = typeof(TInput).FullName;
        string typeOutName = typeof(TOutput).FullName;
        string namespaceName = typeof(CodeProvider).Namespace;
        string fullClassName = namespaceName + "." + className;

        LastError = String.Empty;

        CSharpCodeProvider codeCompiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });
        CompilerParameters parameters = new CompilerParameters(assemblies)
                                            {
                                                GenerateExecutable = false,
                                                GenerateInMemory = true,
                                                CompilerOptions = "/optimize"
                                            };
        string path = Assembly.GetExecutingAssembly().Location;
        parameters.ReferencedAssemblies.Add(path);
        if (typeof(CodeProvider).Assembly.Location != path)
            parameters.ReferencedAssemblies.Add(typeof(CodeProvider).Assembly.Location);
        string executerName = typeof(IXyFunction<TInput, TOutput>).FullName;
        executerName = executerName.Substring(0, executerName.IndexOf('`'));

        code = @"               using System;

            namespace " + namespaceName + @"
            {     
                public class " + className + @" : " + executerName + "<" + typeInName + ", " + typeOutName + @">
                {
                    public " + typeOutName + @" Run(" + typeInName + @" x, " + typeInName + @" y)
                    {"
               + code + @"
                    }
                }
            }";

        CompilerResults results = codeCompiler.CompileAssemblyFromSource(parameters, code);
        if (results.Errors.HasErrors)
        {
            System.Text.StringBuilder err = new System.Text.StringBuilder(512);
            foreach (CompilerError error in results.Errors)
                err.Append(string.Format("Line: {0:d}, Error: {1}\r\n", error.Line, error.ErrorText));
            Console.WriteLine(err);

            LastError = err.ToString();
            return null;
        }
        object objMacro = results.CompiledAssembly.CreateInstance(fullClassName);
        if (objMacro == null)
            throw new ApplicationException(ERROR + " class " + className);

        return (IXyFunction<TInput, TOutput>)objMacro;
    }
}

用法:

IXyFunction<int, int> dynMethod = CodeProvider.Generate<int, int>("return x + y;");
Console.WriteLine(dynMethod.Run(5, 10));

我在 .NET 3.5 解决方案中使用此代码段,如果您使用其他版本,请调整codeCompiler变量。

于 2014-10-22T11:44:58.677 回答
1

您可以使用CodeDOM为您编译代码,然后使用反射运行它。

        // create compiler
        CodeDomProvider provider = CSharpCodeProvider.CreateProvider("C#");
        CompilerParameters options = new CompilerParameters();
        // add more references if needed
        options.ReferencedAssemblies.Add("system.dll");
        options.GenerateExecutable = false;
        options.GenerateInMemory = true;
        // compile the code
        string source = ""; // put here source
        CompilerResults result = provider.CompileAssemblyFromSource(options, source);
        if (!result.Errors.HasErrors)
        {
            Assembly assembly = result.CompiledAssembly;
            // instance can be saved and then reused whenever you need to run the code
            var instance = assembly.CreateInstance("Bla.Blabla");
            // running some method
            MethodInfo method = instance.GetType().GetMethod("Test"));
            var result = (bool)method.Invoke(null, new object[] {});
            // untested, but may works too
            // dynamic instance = assembly.CreateInstance("Bla.Blabla");
            // var result = instance.Test();
        }

这里的重点是要source正确创建。它可以很简单

using System;
namespace Bla
{
    public class Blabla
    {
        public static int Test()
        {
            return 1 + 2;
        }
    }
}

这是一个单行字符串

使用 System;namespace Bla {public class Blabla { public static int Test() { return 1+2; }}}

您可以为预编译函数提供参数或拥有多个此类函数

public static double Test(double x, double y)
{
    return x + y;
}

并像这样调用它

var result = (double)method.Invoke(null, new object[] {x, y});
于 2014-10-22T11:58:03.340 回答
0

受到答案的启发,我决定为我的案例调整Eval Function以获得以下解决方案:

// user defined function: public double UserFunc(double x, double y, string str)
public static object UserDefinedFunc(string UserCode, object[] Parameters) 
{
    CSharpCodeProvider c = new CSharpCodeProvider();
    ICodeCompiler icc = c.CreateCompiler();
    CompilerParameters cp = new CompilerParameters();

    cp.ReferencedAssemblies.Add("system.dll");
    cp.CompilerOptions = "/t:library";
    cp.GenerateInMemory = true;

    StringBuilder sb = new StringBuilder("");
    sb.Append("using System;\n");
    sb.Append("namespace CSCodeEvaler{ \n");
    sb.Append("public class CSCodeEvaler{ \n");

    // start function envelope
    sb.Append("public double UserFunc(double x, double y, string str){ \n");
    sb.Append("double z; \n");

    // enveloped user code
    sb.Append(UserCode + "\n");

    // close function envelope
    sb.Append("return z; \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("UserFunc");

    object s = mi.Invoke(o, Parameters);
    return s;
}

上面的评估方法可以调用如下:

// user defined function entered as text
string code_user_func = @"if (str == ""sum"")
        z = x + y;
    else
        z = Math.Sqrt(x + y + 5);";

// Parameter values
object [] Parameters = new object[] { 5.2, 6.5, "sum" };

// call evaluation method
double r = (double) UserDefinedFunc(code_user_func, Parameters);

// show result
MessageBox.Show("Result with UserDefinedFunc: " + r);
于 2014-10-22T14:22:15.183 回答