(Answer towards the bottom)
I'm trying to build a system that combines
Func<T, bool>
delegates into an ExpressionTree that allows me to pass in a value (badValue in this case) and get a bool out if the predicates all return true and the binary operations are taken into account. This is my first time using Expression/ExpressionTrees, so please be gentle.
I'm getting this error:
ArgumentException: Expression of type 'System.Boolean' cannot be invoked
on this line:
collectAnswers = Expression.And(isEmpty.Body, Expression.Invoke(...
I've got that line set up that way because I need to share the reference to value
among all the Expressions (right?).
My ideal scenario is to just have a bunch of
Expression<Func<blah, blah, bool>>
and I can pass these into a system alongside logical operators (And/Or/Not) and get a bool out at the end. Hoping to allow a dynamic building of rules that a value has to pass through.
Is that even possible in the route I'm going? If not, a couple of pointers directing me down the right path would be appreciated.
string badValue = "hello!";
const int minSize = 8;
const int maxSize = 30;
Expression<Func<string, bool>> stringLengthMax = value => value.Length < maxSize;
Expression<Func<string, bool>> stringLengthMin = value => value.Length > minSize;
Expression<Func<string, bool>> isEmpty = value => !string.IsNullOrEmpty(value);
BinaryExpression collectAnswers = Expression.And(stringLengthMax.Body, Expression.Invoke(stringLengthMin, stringLengthMax.Parameters));
collectAnswers = Expression.And(isEmpty.Body, Expression.Invoke(collectAnswers, stringLengthMax.Parameters));
Func<string, bool> shouldValidate = Expression.Lambda<Func<string, bool>>(collectAnswers, stringLengthMax.Parameters).Compile();
bool result = shouldValidate(badValue);
Answer I wasn't pushing through the parameter(s) the right way, below is an example of multiple parameters shared among several Expressions that are put into an ExpressionTree and a single boolean comes out of the compiled Func, isValid
const int minSize = 8;
const int maxSize = 30;
Expression<Func<string, int, bool>> stringLengthMax = (value, max) => value.Length <= max;
Expression<Func<string, int, bool>> stringLengthMin = (value, min) => value.Length >= min;
Expression<Func<string, bool>> isEmpty = value => string.IsNullOrEmpty(value);
ParameterExpression valueParameter = Expression.Parameter(typeof(string));
ParameterExpression minParameter = Expression.Parameter(typeof(int));
ParameterExpression maxParameter = Expression.Parameter(typeof(int));
Expression<Func<string, int, int, bool>> minMaxCheck =
Expression.Lambda<Func<string, int, int, bool>>(
Expression.And(Expression.Invoke(stringLengthMax, valueParameter, maxParameter),
Expression.Invoke(stringLengthMin, valueParameter, minParameter)), valueParameter, minParameter, maxParameter);
minMaxCheck = Expression.Lambda<Func<string, int, int, bool>>(
Expression.And(Expression.Invoke(minMaxCheck, valueParameter, minParameter, maxParameter),
Expression.Not(Expression.Invoke(isEmpty, valueParameter))), valueParameter, minParameter, maxParameter);
Func<string, int, int, bool> isValid = minMaxCheck.Compile();
bool resultFalse1 = isValid("hello!", minSize, maxSize); // false - too short
bool resultTrue1 = isValid("hello!", "hello!".Length, maxSize); // true - adjust min
bool resultFalse2 = isValid("1234567890123456789012345678901", minSize, maxSize); // false - too long
bool resultTrue2 = isValid("1234567890123456789012345678901", minSize, "1234567890123456789012345678901".Length); // true - adjust max
bool resultFalse3 = isValid(string.Empty, minSize, maxSize); // false - empty
bool shouldBeTrue = isValid("1234567890", minSize, maxSize); // true - just right
bool resultFalse4 = isValid("1234567890", maxSize, maxSize); // false - adjust min
bool resultFalse5 = isValid("1234567890", minSize, minSize); // false - adjust max