I have a list of conditions composed of two Funcs:
public Func<TConfiguration, string> ConfigurationField { get;}
public Func<TNumbering, string> NumberingField { get; }
For each condition, expression would look like this:
Expression<Func<TNumbering, TConfiguration, bool>> (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)
I need to chain the list of these expressions with OrElse.
I tried doing something like:
BinaryExpression expression = null;
foreach (var criteria in SelectionCriteria)
{
Expression<Func<TNumbering, TConfiguration, bool>> exp = (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n);
expression = expression == null ? exp : Expression.OrElse(expression, exp);
}
if (expression == null) return Result.Failure("Expression not defined"));
var lambda = Expression.Lambda<Func<TConfiguration, bool>>(expression);
numberingsToRemove = numberings.Where(_ => configurations.All(lambda));
However, compiler doesn't like it, says there is no implicit conversion between Expression.Lambda<Func<TConfiguration, bool>> and Binary expression.
If I use
expression = expression == null ? Expression.OrElse(exp, exp) : Expression.OrElse(expression, exp);
I get
The binary operator OrElse is not defined for the types 'System.Func<TNumbering,TConfiguration,System.Boolean> and 'System.Func<TNumbering,TConfiguration,System.Boolean>.
I am new to building expressions, can somebody point me in the right direction how to do this?
Your Expression<Func<TNumbering, TConfiguration, bool>>
is a generic type whose open generic type is Expression<TDelegate>, where TDelegate is some delegate type; in this case Func<TNumbering, TConfiguration, bool>
.
Expression<TDelegate> inherits from LambdaExpression, which represents a C# (or VB.NET) lambda expression.
Just like you couldn't write the following code:
var result =
(n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
(n1, c1) => criteria.ConfigurationField(c1) != criteria.NumberingField(n1);
trying to combine two LambdaExpressions with OrElse
would throw an exception at runtime.
Your code isn't even compiling, because expression
is typed as BinaryExpression, meaning an expression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)
into which you're trying to put a full Expression<TDelegate>, which includes (for example) the parameter list.
Every LambdaExpression has a Body property, which extracts from an expression corresponding to this:
(n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)
the body of the LambdaExpression, or an expression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n)
which, in theory, you could then combine into a BinaryExpression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)
But this wouldn't work either, because each iteration introduces multiple new parameters, all of which you'd have to pass in to the final lambda.
It's possible to work around this problem, but I would suggest first and foremost you map each element in SelectionCriteria
to an expression corresponding to the criteria evaluation, using the factory methods at System.Linq.Expressions.Expression. You could then combine those expressions into a BinaryExpression which you could then wrap up in a LambdaExpression or even an Expression.
It might look something like this (making some assumptions):
class Criteria<TConfiguration, TNumbering> {
public Func<TConfiguration, string> ConfigurationField { get;}
public Func<TNumbering, string> NumberingField { get; }
}
// using static System.Linq.Expressions.Expression;
var SelectionCritera = new List<Criteria>();
/*
* populate list here
*/
var configParam = Parameter(typeof(TConfiguration));
var numberingParam = Parameter(typeof(TNumbering));
var expressions =
SelectionCriteria.Select(criteria => {
var criteriaExpr = Constant(criteria);
return NotEqual( // !=
Invoke( // ( ... )
PropertyOrField( // .ConfigurationField
criteriaExpr, // criteria
"ConfigurationField"
),
configParam // c
),
Invoke( // ( ... )
PropertyOrField( // .NumberingField
criteriaExpr, // criteria
"NumberingField"
),
numberingParam // n
)
);
})
.ToList();
if (!expressions.Any) { return Result.Failure("Expression not defined")); }
// Combine all the subexpressions using ||
var body = expressions.Aggregate((prev, next) => OrElse(prev, next));
// Create a LambdaExpression
var lmbd = Lambda<Func<TConfiguration, TNumbering, bool>>(body, configParam, numberingParam);
// Create a .NET method from the LambdaExpression
var mthd = lmbd.Compile();
// Apply the method to each config/numbering pair
var result = (
from config in configs
from numbering in numbering
select (config, numbering)
).All(x => mthd(config, numbering));