Как скомпилировать и выполнить пользовательскую формулу динамически (во время выполнения) в C#?

Я хочу написать кусок кода на C#, который способен компилировать и выполнять пользовательские формулы, введенные в виде строки (действительный код 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);

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 переменная.

Ты можешь использовать 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;
        }
    }
}

Который является строкой в ​​одну строку

using System;namespace Bla {открытый класс 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});

Вдохновленный ответами, я решил настроить 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);
Другие вопросы по тегам