Click here to Skip to main content
15,884,032 members
Articles / Programming Languages / C# 4.0

Dynamic Expresso

Rate me:
Please Sign up or sign in to vote.
4.91/5 (44 votes)
2 Feb 2013MIT11 min read 80.3K   935   72  
Generate LambdaExpression or function delegate on the fly without compilation.
This is an old version of the currently published article.

Introduction

beta version
Official github repository

Dynamic Expresso is an expression interpreter for simple C# statements. Dynamic Expresso embeds its own parsing logic, and really interprets C# statements by converting it to .NET delegates that can be invoked as any standard delegate. It doesn't generate assembly but it creates dynamic expressions/delegates on the fly.

Using Dynamic Expresso developers can create scriptable applications and execute .NET code without compilation.
Statements are written using a subset of C# language specifications. Global variables or parameters can be injected and used inside expressions.

dynamic expresso workflow

Here an example of what you can do:

var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");

or

var interpreter = new Interpreter()
                .SetVariable("service", new ServiceExample());
 
string expression = "x > 4 ? service.SomeMethod() : service.AnotherMethod()";
 
Lambda parsedExpression = interpreter.Parse(expression, 
                        new Parameter("x", typeof(int)));
 
parsedExpression.Invoke(5);

Live demo

Dynamic Expresso live demo: http://dynamic-expresso.azurewebsites.net/

Quick start

Dynamic Expresso is available on NuGet. You can install the package using:

PM> Install-Package DynamicExpresso.Core

Source code and symbols (.pdb files) for debugging are available on Symbol Source.

Features

  • Expressions can be written using a subset of C# syntax (see Syntax section for more information)
  • Support for variables and parameters
  • Full suite of unit tests
  • Good performance compared to other similar projects
  • Small footprint, generated expressions are managed classes, can be unloaded and can be executed in a single appdomain
  • Easy to use and deploy, it is all contained in a single assembly without other external dependencies
  • 100 % managed code written in C# 4.0
  • Open source (MIT license)

Return value

You can parse and execute void expression (without a return value) or you can return any valid .NET type.
The built-in parser can understand the return type of any given expression so you can check if the expression returns what you expect.

Variables

Variables can be used inside your expressions using the Interpreter.SetVariable method:

var target = new Interpreter()
                .SetVariable("myVar", 23);
 
Assert.AreEqual(23, target.Eval("myVar"));

Variables can be primitive types or custom complex types (classes, structures, delegates, arrays, collections, ...).

Custom functions can be passed with delegate variables using Interpreter.SetFunction method:

Func<double, double, double> pow = (x, y) => Math.Pow(x, y);
var target = new Interpreter()
            .SetFunction("pow", pow);
 
Assert.AreEqual(9.0, target.Eval("pow(3, 2)"));

Custom Expression can be passed by using Interpreter.SetExpression method.

Parameters

Parsed expressions can accept a variable number of parameters:

var interpreter = new Interpreter();
 
var parameters = new[] {
                new Parameter("x", 23),
                new Parameter("y", 7)
                };
 
Assert.AreEqual(30, interpreter.Eval("x + y", parameters));

Parameters can be primitive value types or custom complex types. You can parse an expression once and invoke it multiple times with different values:

var target = new Interpreter();
 
var parameters = new[] {
                new Parameter("x", typeof(int)),
                new Parameter("y", typeof(int))
                };
 
var myFunc = target.Parse("x + y", parameters);
 
Assert.AreEqual(30, myFunc.Invoke(23, 7));
Assert.AreEqual(30, myFunc.Invoke(32, -2));

Built-in types and custom types

Currently the predefined types available in any expression are:

Object object 
Boolean bool 
Char char
String string
SByte Byte byte
Int16 UInt16 Int32 int UInt32 Int64 long UInt64 
Single Double double Decimal decimal 
DateTime TimeSpan
Guid
Math Convert

You can easily reference any custom .NET type by using Interpreter.Reference method:

var target = new Interpreter()
                .Reference(typeof(Uri));
 
Assert.AreEqual(typeof(Uri), target.Eval("typeof(Uri)"));
Assert.AreEqual(Uri.UriSchemeHttp, target.Eval("Uri.UriSchemeHttp"));

Syntax and operators

All statments can be written using a subset of the C# syntax. Here a list of the supported operators:

Operators

CategoryOperators
Primaryx.y f(x) a[x] new typeof
Unary+ - ! (T)x
Multiplicative* / %
Additive+ -
Relational and type testing< > <= >= is as
Equality== !=
Conditional AND&&
Conditional OR||
Conditional?:

Literals

CategoryOperators
Constantstrue false null
Numericf m
String/char"" ''

Generate dynamic delegates

You can use the Interpreter.Parse<TDelegate> method to directly parse an expression into a .NET delegate type that can be normally invoked.
In the example below I generate a Func<Customer, bool> delegate that can be used in a LINQ where expression.

class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
    public char Gender { get; set; }
}
 
[TestMethod]
public void Linq_Where()
{
    var customers = new List<Customer> { 
                            new Customer() { Name = "David", Age = 31, Gender = 'M' },
                            new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
                            new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
                            new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
                            new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
                            };
 
    string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";
 
    var interpreter = new Interpreter();
    Func<Customer, bool> dynamicWhere = interpreter.Parse<Func<Customer, bool>>(whereExpression, "customer");
 
    Assert.AreEqual(1, customers.Where(dynamicWhere).Count());
}

This is the preferred way to parse an expression that you known at compile time what parameters can accept and what value must return.

Performance and multithreading

The Interpreter class can be used by multiple threads but without modify it.
In essence only Parse and Eval methods are thread safe. Other methods (SetVariable, Reference, ...) must be called in an initialization phase.
Lambda and Parameter classes are completely thread safe.

If you need to run the same expression multiple times with different parameters I suggest to parse it one time and then invoke the parsed expression multiple times.

Security

If you allow an end user to write expression you must consider some security implications.

Parsed expressions can access only the .NET types that you have referenced using the Interpreter.Reference method or types that you pass as a variable or parameter.
You must pay attention of what types you expose.
In any case generated delegates are executed as any other delegate and standard security .NET rules can be applied (for more info see Security in the .NET Framework).

Usage scenarios

Here are some possible usage scenarios of Dynamic Expresso:

  • Programmable applications

    I have used Dynamic Expresso to allow an user to interact with a console like interface.
    See live demo: http://dynamic-expresso.azurewebsites.net/ or source code on github

  • Allow the user to inject customizable rules and logic without recompiling

    In a tool used to collect Performance Counter data I have used Dynamic Expresso to filter and transform the output data.
    In this way the user can insert custom filter or transform logic. For more information see Counter Catch.

  • Evaluate dynamic functions or commands

  • LINQ dynamic query

Future roadmap

  • Allow to reference types using full name (support namespace and Using method)
  • Extend the Web Shell project to allow its use in an external application
  • Support throw operator
  • Support generic type declaration
  • Best error messages

Help and support

If you need help you can try one of the following:

Credits

This project is based on two old works:

Other resources or similar projects

Below you can find a list of some similar projects that I have evaluated or that can be interesting to study.
For one reason or another none of these projects exactly fit my needs so I decided to write my own interpreter.

License

MIT License

Copyright (c) 2013 Davide Icardi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

  • The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  • THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This article was originally posted at https://github.com/davideicardi/DynamicExpresso

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.