3

(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
Chad Jessup
  • 540
  • 7
  • 15
  • Do you need to do this through `Expressions`? Wouldn't doing something like `collectedAnswers = x => collectedAnswers(x) && currentAnswer(x);` in a loop work for you? (This code wouldn't actually work, it's just to get the idea.) – svick May 23 '13 at 11:43
  • I don't know if it has to be Expression and ExpressionTrees, but I know that [Wikipedia](http://en.wikipedia.org/wiki/Binary_expression_tree) shows something similar to what I'm desiring. I'd like to have hundreds of those Func<>s chained together with logical operators to pass a value through and to get a boolean out if variables made it through them all (with AND/OR/NOT, etc taken into account). This would be dynamic during runtime, or at least set up preruntime but would need to be not-hand combined via the logical operators. So, making a custom linq per combination wouldn't work. – Chad Jessup May 23 '13 at 16:13

2 Answers2

4

If you wanted to do it with Expressions, something like this would work. This doesn't short circuit, though you can build that in. You were quite close. You need to thread one parameter expression through the whole parameter tree.

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);

ParameterExpression pe = Expression.Parameter(typeof(string));

var x = Expression.Lambda<Func<string, bool>>(
    Expression.And(Expression.Invoke(stringLengthMax, pe), 
        Expression.And(Expression.Invoke(stringLengthMin, pe), Expression.Invoke(isEmpty, pe))), pe);

Func<string, bool> shouldValidate = x.Compile();
bool resultFalse1 = shouldValidate("hello!");
bool resultFalse2 = shouldValidate("1234567890123456789012345678901");
//bool resultFalse3 = shouldValidate(null); Throws an exception because you can't do (null).Length
bool shouldBeTrue = shouldValidate("123456789");

//LinqPad code to view results:
resultFalse1.Dump();
resultFalse2.Dump();
//resultFalse3.Dump();
shouldBeTrue.Dump();
Shlomo
  • 14,102
  • 3
  • 28
  • 43
  • This did it. Updating the question with code to demonstrate more fully what I was hoping for. Thank you! – Chad Jessup May 23 '13 at 19:18
  • I really don't see what do `Expression`s give here that couldn't be accomplished using delegates more succinctly. – svick May 23 '13 at 19:34
  • 1
    I'm too new to Expressions to thoroughly explain why I think these are the right direction. But, I'm wanting to dynamically build a delegate based on Func's passed into a system alongside logical operators. If delegates can be dynamically combined another way I'd love to hear about it. I was under the impression that this is a/the route to do this. I edited original question to hopefully further explain my needs. – Chad Jessup May 23 '13 at 19:58
1

It seems to me that you don't really need to build an expression tree at all. You can combine your funcs using simple linq (define them as Func<string, bool>, not Expression<Func<string, bool>>):

Func<string, bool> shouldValidate = 
    arg => new[] {stringLengthMax, stringLengthMin, isEmpty}.All(func => func(arg));

If you want to use more than just and, you can combine your funcs logically:

Func<string, bool> shouldValidate = 
    arg => isEmpty(arg) || (stringLengthMax(arg) && stringLengthMin(arg));
Simon MᶜKenzie
  • 8,344
  • 13
  • 50
  • 77
  • How do you recommend I do the logical operators here without modifying linq? That is something like: isEmpty || (stringLengthMax && stringLengthMin). Also, I didn't know you could do that with linq, but I'm unable to get it to compile: Cannot assign lambda expression to an implicitly-type local variable and 'func' is a 'variable' but is used like a 'method' - changing the Expression> to just Func gives me similar, but different, errors. That's a powerful concept, and understanding it more would be helpful. – Chad Jessup May 23 '13 at 03:34
  • Sorry for the syntax error. I've updated the answer based on your comments. Note that I'm assuming that your funcs will be `Func` rather than expressions, since you don't need expressions if you're not building an expression tree. – Simon MᶜKenzie May 23 '13 at 04:21
  • Thank you for clearing that up. I think I've poorly explained myself, though. Ideally, I'd have hundreds of those 'Func' and during runtime they're passed into an object alongside logical operators to build a complex logical relationship that value (and other variables) have to pass through to get a true of false out of. I'm not sure if your answer would allow me to do that, please correct me if I'm wrong. I don't know if Expression and ExpressionTrees are the right way for this, but the needs seem to match up with what I view as an ExpressionTree. – Chad Jessup May 23 '13 at 16:06
  • @ChadJessup In that case, you might want to post a new question where you properly describe what you need, along with some examples. – svick May 23 '13 at 19:32
  • @svick I felt that the question at the core of my problem was the best route to ask for help. A lot of business needs, or "I want to dynamically combine funcs" didn't feel like the best route to take. I did edit the original question to more fully explain my needs, though. – Chad Jessup May 23 '13 at 20:01