views:

279

answers:

5

is it possible to create an anonymous method in c# from a string?

e.g. if I have a string "x + y * z" is it possible to turn this into some sort of method/lambda object that I can call with arbitrary x,y,z parameters?

+5  A: 

C# doesn't have any functionality like this (other languages - like JavaScript - have eval functions to handle stuff like this). You will need to parse the string and create a method yourself with either expression trees or by emitting IL.

Andrew Hare
+12  A: 

It's possible, yes. You have to parse the string and, for example, compile a delegate using expression trees.

Here's an example of creating (x, y, z) => x + y * z using expression trees:

ParameterExpression parameterX = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterY = Expression.Parameter(typeof(int), "y");
ParameterExpression parameterZ = Expression.Parameter(typeof(int), "z");
Expression multiplyYZ = Expression.Multiply(parameterY, parameterZ);
Expression addXMultiplyYZ = Expression.Add(parameterX, multiplyYZ);
Func<int,int,int,int> f = Expression.Lambda<Func<int, int, int, int>>
(
    addXMultiplyYZ,
    parameterX,
    parameterY,
    parameterZ
).Compile();
Console.WriteLine(f(24, 6, 3)); // prints 42 to the console
Jason
+1 This is a good example but is tailored to the string in question. This example wouldn't help the OP parse a random string, infer the types of the identifiers in the string, and create a method based on what was found. Nevertheless, +1 to you for the good example.
Andrew Hare
+1, and here's to 9999 no longer!
Jeff Sternal
I assumed that parsing would be more familiar (at least the literature on parsing is larger) to the OP than expression trees. The purpose of the expression tree is solely to show the technology to him and to show their power, not to solve the general problem.
Jason
+1  A: 

It could be possible with a grammar (e.g. ANTLR) and an interpreter which creates expression trees. This is no small task, however, you can be successful if you limit the scope of what you accept as input. Here are some references:

Here is what some code may look like to transform an ANTLR ITree into an Expression tree. It isn't complete, but shows you what you're up against.

private Dictionary<string, ParameterExpression> variables
    = new Dictionary<string, ParameterExpression>();

public Expression Visit(ITree tree)
{
    switch(tree.Type)
    {
        case MyParser.NUMBER_LITERAL:
            {
                float value;
                var literal = tree.GetChild(0).Text;
                if (!Single.TryParse(literal, out value))
                    throw new MyParserException("Invalid number literal");
                return Expression.Constant(value);
            }

        case MyParser.IDENTIFIER:
            {
                var ident = tree.GetChild(0).Text;
                if (!this.variables.ContainsKey(ident))
                {
                    this.variables.Add(ident,
                       Expression.Parameter(typeof(float), ident));
                }

                return this.variables[ident];
            }

        case MyParser.ADD_EXPR:
            return Expression.Add(Visit(tree.GetChild(0)), Visit(tree.GetChild(1)));

        // ... more here
    }
}
sixlettervariables
+2  A: 

There are functionality to do this in the .Net framework.

It is not easy. You need to add some code around the statement to make it into a complete assembly including a class and method you can call.

After that you pass the string to

CSharpCodeProvider.CompileAssemblyFromSource(options, yourcode);

Here is an example

adrianm
Along with figuring out what parameters are required, etc. Tough to make friendly with CompileAssemblyFromSource.
sixlettervariables
+3  A: 

Just for fun using CodeDom (any valid C# code is allowed in the string as long as it is present in mscorlib (No check for errors at all):

static class Program
{
    static string code = @"
        public static class __CompiledExpr__
        {{
            public static {0} Run({1})
            {{
                return {2};
            }}
        }}
        ";

    static MethodInfo ToMethod(string expr, Type[] argTypes, string[] argNames, Type resultType)
    {
        StringBuilder argString = new StringBuilder();
        for (int i = 0; i < argTypes.Length; i++)
        {
            if (i != 0) argString.Append(", ");
            argString.AppendFormat("{0} {1}", argTypes[i].FullName, argNames[i]);
        }
        string finalCode = string.Format(code, resultType != null ? resultType.FullName : "void",
            argString, expr);

        var parameters = new CompilerParameters();
        parameters.ReferencedAssemblies.Add("mscorlib.dll");
        parameters.ReferencedAssemblies.Add(Path.GetFileName(Assembly.GetExecutingAssembly().Location));
        parameters.GenerateInMemory = true;

        var c = new CSharpCodeProvider();
        CompilerResults results = c.CompileAssemblyFromSource(parameters, finalCode);
        var asm = results.CompiledAssembly;
        var compiledType = asm.GetType("__CompiledExpr__");
        return compiledType.GetMethod("Run");
    }

    static Action ToAction(this string expr)
    {
        var method = ToMethod(expr, new Type[0], new string[0], null);
        return () => method.Invoke(null, new object[0]);
    }

    static Func<TResult> ToFunc<TResult>(this string expr)
    {
        var method = ToMethod(expr, new Type[0], new string[0], typeof(TResult));
        return () => (TResult)method.Invoke(null, new object[0]);
    }

    static Func<T, TResult> ToFunc<T, TResult>(this string expr, string arg1Name)
    {
        var method = ToMethod(expr, new Type[] { typeof(T) }, new string[] { arg1Name }, typeof(TResult));
        return (T arg1) => (TResult)method.Invoke(null, new object[] { arg1 });
    }

    static Func<T1, T2, TResult> ToFunc<T1, T2, TResult>(this string expr, string arg1Name, string arg2Name)
    {
        var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2) },
            new string[] { arg1Name, arg2Name }, typeof(TResult));
        return (T1 arg1, T2 arg2) => (TResult)method.Invoke(null, new object[] { arg1, arg2 });
    }

    static Func<T1, T2, T3, TResult> ToFunc<T1, T2, T3, TResult>(this string expr, string arg1Name, string arg2Name, string arg3Name)
    {
        var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2), typeof(T3) },
            new string[] { arg1Name, arg2Name, arg3Name }, typeof(TResult));
        return (T1 arg1, T2 arg2, T3 arg3) => (TResult)method.Invoke(null, new object[] { arg1, arg2, arg3 });
    }

    static void Main(string[] args)
    {
        var f = "x + y * z".ToFunc<int, int, long, long>("x", "y", "z");
        var x = f(3, 6, 8);

    }
}
VirtualBlackFox