ExpressionTools is a library that helps you to handle C# lambda expression. It is written in C# and thanks to .Net Standard, it is cross platform.
The project is based on System.Linq.Expressions and uses the C# Roslyn compiler.
Don't hesitate to post issues, pull requests on the project or to fork and improve the project.
Package | Nuget.org | Pre-release |
---|---|---|
SoloX.ExpressionTools.Parser | ||
SoloX.ExpressionTools.Transform |
ExpressionTools project is written by Xavier Solau. It's licensed under the MIT license.
You can checkout this Github repository or you can use the NuGet package:
Install using the command line from the Package Manager:
Install-Package SoloX.ExpressionTools.Parser -version 1.0.5
Install-Package SoloX.ExpressionTools.Transform -version 1.0.5
Install using the .Net CLI:
dotnet add package SoloX.ExpressionTools.Parser --version 1.0.5
dotnet add package SoloX.ExpressionTools.Transform --version 1.0.5
Install editing your project file (csproj):
<PackageReference Include="SoloX.ExpressionTools.Parser" Version="1.0.5" />
<PackageReference Include="SoloX.ExpressionTools.Transform" Version="1.0.5" />
Note that you can find code examples in this repository in this location: src/examples
.
A few lines of code are actually needed to parse a textual C# lambda expression.
Let's say that we want to parse a simple lambda expression like "(int x) => x + 1"
with x as an integer:
// Set the expression to parse
var expToParse = "(int x) => x + 1";
// We need to create the parser.
var expressionParser = new ExpressionParser();
// We can just parse the expression.
var lambdaExpression = expressionParser.Parse(expToParse);
// Or we can parse the expression specifying the type of the lambda expression.
var expression = expressionParser.Parse<Func<int, int>>(expToParse);
The Parse method will return an expression tree (from System.Linq.Expressions
) reflecting the given textual
C# lambda expression.
More precisely, The resulting expression object is based on System.Linq.Expressions.LambdaExpression
.
It means that you can then use the Compile
method to compute a Delegate
in order to call the lambda at full speed.
Unfortunately, things can get a little bit more complicated if you want to parse the same expression without
specifying the parameter type giving us a expression like "x => x + 1"
.
In this case you will need to provide to the ExpressionParser a IParameterTypeResolver
that will resolve
the type of the parameter x
as an integer:
// Set the expression to parse
var expToParse = "x => x + 1";
// We need to create the parser with a DictionaryParameterTypeResolver that will resolve the
// parameter name using the given Dictionary.
var expressionParser = new ExpressionParser(
parameterTypeResolver: new DictionaryParameterTypeResolver(new Dictionary<string, Type>()
{
{ "x", typeof(int) },
}));
// We can just parse the expression.
var expression = expressionParser.Parse<Func<int, int>>(expToParse);
Let's now use a static method Max
that is defined in System.Math
with a full qualified name like
"(double x, double y) => Math.Max(x, y)"
.
To support this use case, you will need to provide a ITypeNameResolver
that will resolve the Math
as System.Math
class:
// Set the expression to parse
var expToParse = "(double x, double y) => Math.Max(x, y)";
// We need to create the parser with a TypeNameResolver that will resolve type name with
// the given System.Math class.
var expressionParser = new ExpressionParser(
typeNameResolver: new TypeNameResolver(typeof(Math)));
// We can just parse the expression.
var expression = expressionParser.Parse<Func<double, double, double>>(expToParse);
Now we want our expression to use a static method Max
that is defined in System.Math
omitting the class name prefix like
"(double x, double y) => Max(x, y)"
.
To support this use case, you will need to provide a IMethodResolver
that will resolve the Max
as System.Math.Max
:
// Set the expression to parse
var expToParse = "(double x, double y) => Max(x, y)";
// We need to create the parser with a StaticMethodResolver that will resolve methods with
// the System.Math class.
var expressionParser = new ExpressionParser(
methodResolver: new StaticMethodResolver(typeof(Math)));
// We can just parse the expression.
var expression = expressionParser.Parse<Func<double, double, double>>(expToParse);
In the case where we would like the replace the parameter a
from one expression like "a => a * 2"
by another
expression like "b => b + 1"
resulting in "b => (b + 1) * 2"
, we can use the SingleParameterInliner
.
// Setup the expressions to use as input
Expression<Func<int, int>> expressionToAmend = a => a * 2;
Expression<Func<int, int>> expressionToInline = b => b + 1;
// create the expression parameter in-liner.
var inliner = new SingleParameterInliner();
// Amend the given expression replacing parameter 'a' with the expression to in-line resulting in the
// lambda 'b => (b + 1) * 2'.
var inlinedExpression = inliner.Amend<Func<int, int>, Func<int, int>>(expressionToInline, expressionToAmend);
In the case where we would like to amend an expression like "(a, b) => a * b"
replacing the two parameter 'a' and 'b'
with two another expressions:
- using
"c => c + 1"
to replace 'a' - and using
"d => d - 1"
to replace 'b'
all this resulting in "(c, d) => (c + 1) * (d - 1)"
, we can use the MultiParameterInliner
.
All it needs is a ParameterResolver
that will be used to get the expression to in-line instead of the parameter itself:
// Setup the expressions to use as input
Expression<Func<int, int>> expressionToAmend = (a, b) => a * b;
Expression<Func<int, int>> expressionToInlineForA = c => c + 1;
Expression<Func<int, int>> expressionToInlineForB = d => d - 1;
// create the expression in-liner.
var inliner = new ExpressionInliner();
// Setup the resolver telling that 'a' must be replaced by in-lined 'c => c + 1' lambda
// and that 'b' must be replaced by in-lined 'd => d - 1' lambda.
var resolver = new ParameterResolver()
.Register("a", expressionToInlineForA)
.Register("b", expressionToInlineForB);
// Amend the given expression replacing parameters resulting in the lambda '(c, d) => (c + 1) * (d - 1)'.
var inlinedExpression = inliner.Amend<Func<int, int>, Func<int, int>>(resolver, expressionToAmend);
Let's say that we need to convert a Lambda Expression using an external variable like this:
// Here is a variable we are going to use in a Lambda Expression.
var externalValue = 0.01d;
// The expression is multiplying the input with the variable 'externalValue'.
Expression<Func<double, double>> expToInline = i => i * externalValue;
The use case here is that we want to convert the expression in-lining the actual value of the variable like
i => i * 0.01d
.
It has never been simpler with the ConstantInliner
: all we need is to call its Amend
method:
// Create the constant in-liner.
var inliner = new ConstantInliner();
// Amend the expToInline.
var exp = inliner.Amend(expToInline);
// That's yet, 'exp' is equal to 'i => i * 0.01d'
In order to get the name of a property from a lambda like i => i.MyProperty
we can use the PropertyNameResolver
:
// Create a instance of the resolver.
var resolver = new PropertyNameResolver();
// Resolve the property name of the given lambda.
var name = resolver.GetPropertyName<IMyType, string>(x => x.MyProperty);
// Here name is set to "MyProperty".
To serialize your expression as a string, you can use the Serialize method like this:
using SoloX.ExpressionTools.Transform;
Expression<Func<double, double, double>> expression = (a, b) => Math.Pow(a, b);
var txt = expression.Serialize();
// will give you the string:
// "(a, b) => Math.Pow(a, b)"