DynamicExpresso: a C# expression interpreter, and where its subset stops
C# expressions interpreter
At a glance
- What is it?
- DynamicExpresso interprets a partial subset of C# into expression trees and delegates without generating an assembly, aimed at scriptable applications, configuration-time rules and dynamic LINQ. It is small, dependency-free and MIT licensed, and its honest feature list is also its boundary: generics, extension methods, dynamic and lambdas all work partially.
- Who is it for?
- Adopt DynamicExpresso when a rule has to be editable at runtime by someone who will not compile C#, parse it once with typed Parameter objects and reuse the resulting Lambda, and treat SetFunction and Reference as the objects you are exposing rather than conveniences. Do not adopt it for fixed hot-path logic, where a compiled lambda is faster and safer.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An interpreter for a deliberately narrow slice of C#
Dynamic Expresso is an interpreter for simple C# statements, written in .NET Standard 2.0. The key word is simple. Statements are written using a subset of the C# language specification, so an expression that compiles in a normal project can still be rejected here for using a construct the interpreter does not implement.
The stated use cases are three: scriptable applications, executing .NET code without compilation, and dynamic LINQ statements. All three are the same mechanism seen from different angles. You hand the interpreter a string, it parses that string with its own embedded parser, and it produces something callable.
That framing matters for the security question, because it is not one the README asks. An interpreter that can call methods on the objects you give it is a script engine, not a sandbox. Variables can be primitive types or custom complex types, including classes, structures, delegates, arrays and collections, and a delegate variable registered through `SetFunction` becomes directly callable from the expression string. If expressions come from users rather than from your own configuration, the trust boundary has to be built around what you expose, not around the interpreter.
No assembly is generated, an expression tree is
The README is precise about the mechanism. Dynamic Expresso embeds its own parsing logic, interprets C# statements by converting them to .NET lambda expressions or delegates, and does not generate an assembly. It creates an expression tree on the fly instead.
The workflow diagram in `docs/` shows the same path. Text goes in a parser, the parser produces a structure the runtime can compile, and the result is a `Lambda` or a typed delegate you invoke like any other.
Three practical consequences follow from that choice. First, there is no build step and no `AssemblyBuilder`, so starting an interpreter does not require dynamic code generation permissions and does not leave compiled artifacts behind. Second, the footprint is described as small because generated expressions are managed classes that can be unloaded and can run in a single appdomain. Third, the whole thing ships as a single assembly with no other external dependencies, which is what makes it droppable into an existing application without a dependency negotiation.
The cost is that nothing is specialised for your expression. An interpreter builds a tree each time it parses, and a tree that a compiler would have folded into constants stays a tree until the runtime handles it.
Installing the package and evaluating the first expression
Dynamic Expresso ships on NuGet as `DynamicExpresso.Core`, and the README gives the Package Manager Console form.
PM> Install-Package DynamicExpresso.CoreWith the package referenced, the smallest useful program is two lines.
var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");What you should notice is that nothing declares a return type. `Eval` infers one from the expression. If you want the type fixed in advance, which matters when the expression comes from configuration, pass it as a generic argument and supply parameters by name.
double result = target.Eval<double>("Math.Pow(x, y) + 5",
new Parameter("x", typeof(double), 10),
new Parameter("y", typeof(double), 2));That form is the safer one for anything user-supplied, because a parse failure becomes a type error at evaluation time instead of a cast exception later. A live demo is hosted at dynamic-expresso.azurewebsites.net, and the API documentation and a WebShell sample live in the repository under `docs/` and `sample/DynamicExpressoWebShell/`.
Parse once, invoke many
The performance-relevant path separates parsing from execution. You declare parameters, parse the string, and keep the resulting object for repeated calls with different values.
var parameters = new[] {
new Parameter("x", typeof(int)),
new Parameter("y", typeof(int))
};
var myFunc = target.Parse("x + y", parameters);
Assert.That(myFunc.Invoke(23, 7), Is.EqualTo(30));
Assert.That(myFunc.Invoke(32, -2), Is.EqualTo(30));Evaluating the same string repeatedly would rebuild the tree each time. Holding the `Lambda` means the parsing cost is paid once and the remaining work is invocation. That is the difference between a dynamic filter feature that is fine in a settings screen and one that is fine on every row of a grid.
Identifiers can also be discovered without executing. The feature list includes the ability to discover the variables, types and parameters of a given expression, which is what you need if you want to validate or explain an expression rather than run it, for instance to check that a user-supplied formula references only names you are willing to expose.
The registration surface: SetVariable, SetFunction, Reference
What an expression can reach is exactly what you put on the interpreter. There are four registration methods and each one widens the surface differently.
`SetVariable` binds a name to a value, which is how `myVar` resolves in an expression. `SetFunction` binds a name to a `Func`, so `pow(3, 2)` can call a lambda you registered. `SetExpression` takes a custom LINQ `Expression` object directly. `Reference` makes a type available to the expression, which is what allows `typeof(Uri)` and `Uri.UriSchemeHttp` to resolve.
var target = new Interpreter().Reference(typeof(Uri));
Assert.That(target.Eval("typeof(Uri)"), Is.EqualTo(typeof(Uri)));There is also a special identifier. A variable or parameter named `this` can be referenced implicitly, so setting `this` to a customer object makes both `this.Name` and a bare `Name` resolve to its property.
The type list the parser knows by default is explicit and finite: `Object`, `Boolean`, `Char`, `String`, the signed and unsigned integer widths from `Byte` to `UInt64`, `Single`, `Double`, `Decimal`, `DateTime`, `TimeSpan`, `Guid`, plus `Math` and `Convert`. Anything beyond that arrives through `Reference`. Reading the four registration methods as one list is the fastest way to understand both the capability and the exposure.
Partial support, stated as partial
The feature list is unusually honest, and the hedges are the useful part.
Generics, `params` arrays and extension methods are partially supported, and only with implicit generic argument detection, so `Foo<T>()` in a user string does not do what it does in compiled C#. `dynamic` is partial in two flavours: `ExpandoObject` covers get properties, method invocation and indexes, while `DynamicObject` covers get properties and indexes only. Lambda expressions inside a string are supported but disabled by default, because the README states they carry a slight performance penalty.
Case sensitivity is configurable, and the default is case sensitive. That catches people out when an expression authored on Windows fails against a differently cased identifier.
The list also claims good performance compared to other similar projects, without a number, a baseline or a method. Treat that as a direction rather than a measurement. What is verifiable from the documentation is the shape that produces performance: parse once, invoke many, and keep the expression in a delegate.
The README states two different sets of build targets
The supported-platforms line near the top of the README says .NET Core 3.1, .NET Core 5.0 and above, and .NET 4.6.2. The feature list further down, under the .NET Standard 2.0 entry, says the build is available for .NET 4.6.1 and .NET Core 2.0.
Those are different claims in the same document, and neither the README nor the repository explains which is current. The .NET Framework figure differs by one patch version and the .NET Core figure differs by two releases. Since the library targets .NET Standard 2.0, it can in principle load on all of them, so the discrepancy is probably about where CI builds and where binaries are published rather than about what the code requires.
That is exactly the question to ask before adopting it, though. If you are on a target where the answer matters, check the actual project files in `src/` and the release assets rather than trusting either sentence.
The repository carries two solutions, `DynamicExpresso.sln` and `DynamicExpressoWebShell.sln`, a `test/` directory matching the README's claim of a full unit test suite, and a `CODEOWNERS` file.
Interpreted expressions versus compiled rules
The realistic alternative is not another library, it is not shipping strings at all. A business rule written as a lambda in your own assembly is compiled once, inlined at the call site, and can be optimised. An expression interpreted at runtime is a tree walked or compiled generically, and it cannot benefit from knowing your types in advance.
The second alternative sits on the other side and gives up the editing experience. Building an actual assembly at runtime from a string, whether with `System.Reflection.Emit` or Roslyn, produces real IL that the JIT can optimise, at the cost of a compilation step, a warm-up delay and a permission requirement for dynamic code generation. Dynamic Expresso deliberately takes neither route. It builds an expression tree, which is the middle ground: no assembly on disk, no compiler invoked, and the runtime still gets to compile a delegate.
Choose the interpreter when the expression has to be editable at runtime by someone who is not a developer, or when it arrives from a configuration file. Choose compiled code when the rule is fixed and performance matters. The README's own three use cases, scriptable applications, code execution without compilation and dynamic LINQ, all sit on the editable side of that line.
Editorial conclusion
Adopt DynamicExpresso when a rule has to be editable at runtime by someone who will not compile C#, parse it once with typed Parameter objects and reuse the resulting Lambda, and treat SetFunction and Reference as the objects you are exposing rather than conveniences. Do not adopt it for fixed hot-path logic, where a compiled lambda is faster and safer. Verify first which frameworks your target actually needs, because the README names .NET 4.6.2 with .NET Core 3.1 in one place and .NET 4.6.1 with .NET Core 2.0 in another.
Frequently asked questions
How do I install Dynamic Expresso in a .NET project?
The README gives the Package Manager Console form, PM> Install-Package DynamicExpresso.Core, and the library targets .NET Standard 2.0. It ships as a single assembly with no other external dependencies.
Does Dynamic Expresso compile the expressions I give it?
No assembly is generated. The interpreter parses the string with its own parser and creates an expression tree on the fly, producing a Lambda or delegate that you invoke.
How do I pass variables or functions into an expression?
Use Interpreter.SetVariable to bind a value, Interpreter.SetFunction to bind a delegate that the expression can call, Interpreter.SetExpression for a custom LINQ Expression, and Interpreter.Reference to make a type such as Uri resolvable by name.
Can I evaluate the same expression many times with different inputs?
Yes. Declare Parameter objects, call Parse once to get a Lambda, then call Invoke repeatedly with new values, as the README example does with an expression returning 30 for both 23 plus 7 and 32 minus 2.
Does Dynamic Expresso support generic methods and extension methods?
Partially. The README lists generics, params arrays and extension methods as only supported with implicit generic argument detection, and lambda expressions as disabled by default because of a slight performance penalty.
Is Dynamic Expresso still being released?
The repository is not archived and the last push was on 2026-09-24, which is also the date of release v2.19.6. Earlier releases v2.19.4 and v2.19.5 both landed on 2026-08-20.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dynamicexpresso-dynamicexpresso)