简体   繁体   中英

How to compile and execute a user defined formula dynamically (runtime) in c#?

I want to write a piece of code in c# which is able to compile and execute user defined formulas entered as string (valid c# code) in a windows form. Is there a simple and elegant way for doing this?

As an example, see the method below:

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

The formula string entered by the user can contain anything provided that it is a valid c# code. In the example above, user enters following string into the text area of parameter form:

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

You could use this code snippet:

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

Usage:

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

I'm using this snippet in the .NET 3.5 solution, if you use another version, adjust the codeCompiler variable.

You can use CodeDOM to compile code for you and then run it by using reflection.

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

Point here is to create source properly. It can be as simple as

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

Which is a one line string

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

You can have parameters to precompiled functions or have a number of such function

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

and invoke it like this

var result = (double)method.Invoke(null, new object[] {x, y});

Inspired by the answers, I decided to adjust the Eval Function for my case to obtain following solution:

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

The evaluation method above can be invoked as follows:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM