Converting String expressions to Funcs with FunctionFactory

I remember looking through some of the included VS2008 samples (here), and there is quite a nice little DynamicQuery project demonstrating the ExpressionParser demo type. It wasn’t long until I started having a play with how we could take advantage of this in code.

The first step was to make a few modifications to the ExpressionParser type, namely changing access modifiers and removing the limitation on using a fixed list of types. Where do we make these changes? Well, first step, let’s make it public.

Expression ParseMemberAccess(Type type, Expression instance) { // ... switch (FindMethod(type, id, instance == null, args, out mb)) { case 0: throw ParseError(errorPos, Res.NoApplicableMethod, id, GetTypeName(type)); case 1: MethodInfo method = (MethodInfo)mb; //if (!IsPredefinedType(method.DeclaringType)) // Comment out this line, and the next. //throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); if (method.ReturnType == typeof(void)) throw ParseError(errorPos, Res.MethodIsVoid, id, GetTypeName(method.DeclaringType)); return Expression.Call(instance, (MethodInfo)method, args); default: throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, GetTypeName(type)); } // ... }
Code language: JavaScript (javascript)

ExpressionParser can take a string expression and convert it to an Expression, which we can in turn create a Delegate for, so given the expression “(a + b)”, if we know the operand types, and the return type, we can create a Delegate. Let’s see how:

var @params = new[] { Expression.Parameter(typeof(int), "a"), Expression.Parameter(typeof(int), "b") }; var parser = new ExpressionParser(@params, "(a + b)", null); var @delegate = Expression.Lambda(parser.Parse(typeof(int)), @params).Compile();
Code language: JavaScript (javascript)


With that small bit of code, we’ve created some parameter expressions, named to match the operands in our expression, and then we use the ExpressionParser to create an Expression instance that represents our string expression. The next step was to compile the Lamda of that expression into an executable Delegate.

The nice thing about the Delegate type, is that it is castable to Func<> instances and makes using the code easy to use. Our example above could be used as such:

Func func = (Func)@delegate; int result = func(1, 2); // Result should be three.
Code language: JavaScript (javascript)


The important thing which I hope you get, is that we’ve taken a simple string expression and converted it into a much more powerful Func<> delegate, which is strongly-typed.

To this end, I’ve started building a FunctionFactory type used to easily generate these Func<> instances, e.g.

var @public static class FunctionFactory { private static Delegate CreateInternal(Type[] argumentTypes, Type returnType, string expression, string[] argumentNames = null) { if (argumentNames != null) { if (argumentTypes.Length != argumentNames.Length) throw new ArgumentException("The number of argument names does not equal the number of argument types."); } var @params = argumentTypes .Select((t, i) => (argumentNames == null) ? Expression.Parameter(t) : Expression.Parameter(t, argumentNames[i])) .ToArray(); ExpressionParser parser = new ExpressionParser(@params, expression, null); var @delegate = Expression.Lambda(parser.Parse(returnType), @params).Compile(); return @delegate; } public static Func<TReturn> Create<TReturn>(string expression, string[] argumentNames = null) { return (Func<TReturn>)CreateInternal(new Type[0], typeof(TReturn), expression, argumentNames); } public static Func<A, TReturn> Create<A, TReturn>(string expression, string[] argumentNames = null) { return (Func<A, TReturn>)CreateInternal(new[] { typeof(A) }, typeof(TReturn), expression, argumentNames); } public static Func<A, B, TReturn> Create<A, B, TReturn>(string expression, string[] argumentNames = null) { return (Func<A, B, TReturn>)CreateInternal(new[] { typeof(A), typeof(B) }, typeof(TReturn), expression, argumentNames); } }
Code language: JavaScript (javascript)


Now we can do some really cool stuff:

string expression = "(1 + 2)"; var func = FunctionFactory.Create<int>(expression); int result = func(); // Result should be 3. expression = "(a * b)"; var func2 = FunctionFactory.Create<int, int, int>(expression, new[] { "a", "b" }); int result = func2(10, 50); // Result should be 500.
Code language: JavaScript (javascript)

We can even go a bit further and handle more complex objects, so if I have an example type, Person with an Age property, we can test against it:

expression = "(Age == 5)"; var func3 = FunctionFactory.Create<Person, bool>(expression); bool isFive = func3(new Person { Age = 5 });
Code language: JavaScript (javascript)


As you can see, the application of these types of expressions is quite endless. Personally I’ve now been able to introduce dynamic expressions into quite complex rule systems to allow for an additional dimension of flexibility.

Please find the demo project attached, it’s not as tidy as usual, but still fun to play with and extend.

Leave a Reply

Your email address will not be published.

Related Post