0

I've had this idea for a while, and I'm curious if it's possible to implement in a worthwhile way. I want to take an array of boolean-returning lambda expressions and perform logical operations on their results. Here's a pointless example of a valid list:

var tests = new List<Func<int, bool>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

What I'd like to do is essentially

bool result = tests.And();

or perform other logical operations. I realize I could write an IEnumerable-implementing class and do this, but I was wondering if it has already been done. Obviously, the implementation has to work efficiently, short-circuiting in the same way as

if (truthyExpression || falseyExpression)

would never get around to evaluating falseyExpression.

The only thing I see in the framework that could maybe be helpful is Bit Array, but I'm not sure how I could use that without pre-evaluating every expression, defeating the usefulness of short-cicuiting.

Andrew
  • 4,145
  • 2
  • 37
  • 42
  • 1
    Could you provide some sample input and output? I'm a little unclear on what you want the output to be. – Abe Miessler Mar 17 '14 at 15:20
  • 1
    It sounds like your only question is "has this already been done", which is not a very good question. If it hasn't been done, then creating an `IEnumerable`-based implementation is the way to go. Are you unsure of how to do that? If so, let us know which issues are tripping you up. Otherwise, it seems like you know enough to solve this on your own. – siride Mar 17 '14 at 15:29
  • @siride, I asked the question not because I didn't know *a* way to do it, but because I didn't know if there was *an accepted way*, or even a good way to do it. I have learned new things from the answers, and I imagine other people may stumble upon this and benefit from it as well, so I think the question has a place here. I'll try to be more clear and specific in the future. – Andrew Mar 17 '14 at 20:21
  • @Andrew: unfortunately, the prevailing thought on this site, for better or worse, is that questions that solicit an opinion or discussion about which way is better or worse are not good questions. – siride Mar 18 '14 at 15:43

4 Answers4

6

You could use the built-in Enumerable.Any and Enumerable.All extension methods.

bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));
khellang
  • 17,550
  • 6
  • 64
  • 84
Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • 3
    @khellang `Enumerable.Any` will "short circuit" if a delegate returns `true` (and return `true` itself), and `Enumerable.All` will "short circuit" if a delegate returns `false` (and return `false` itself). – sloth Mar 17 '14 at 15:31
  • Oops, I meant the other way around :P – khellang Mar 17 '14 at 15:34
3

Sure you could.

The easy way

var tests = new List<Func<int, bool>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

We 're going to aggregate all these predicates into one by incrementally logical-and-ing each one with the already existing result. Since we need to start somewhere, we 'll start with x => true because that predicate is neutral when doing AND (start with x => false if you OR):

var seed = (Func<int, bool>)(x => true);
var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Func<int, bool>)(x => combined(x) && expr(x)));

Console.WriteLine(allTogether.Invoke(30)); // True

That was easy! It does have a few limitations though:

  • it only works on objects (as does your example)
  • it might be a tiny bit inefficient when your list of predicates gets large (all those function calls)

The hard way (using expression trees instead of compiled lambdas)

This will work everywhere (e.g. you can also use it to pass predicates to SQL providers such as Entity Framework) and it will also give a more "compact" final result in any case. But it's going to be much harder to make it work. Let's get to it.

First, change your input to be expression trees. This is trivial because the compiler does all the work for you:

var tests = new List<Expression<Func<int, bool>>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

Then aggregate the bodies of these expressions into one, the same idea as before. Unfortunately, this is not trivial and it is not going to work all the way, but bear with me:

var seed = (Expression<Func<int, bool>>)
    Expression.Lambda(Expression.Constant(true), 
    Expression.Parameter(typeof(int), "x"));

var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Expression<Func<int, bool>>)
        Expression.Lambda(
        Expression.And(combined.Body, expr.Body), 
        expr.Parameters
    ));

Now what we did here was build one giant BinaryExpression expression from all the individual predicates.

You can now pass the result to EF or tell the compiler to turn this into code for you and run it, and you get short-circuiting for free:

Console.WriteLine(allTogether.Compile().Invoke(30)); // should be "true"

Unfortunately, this final step won't work for esoteric technical reasons.

But why won't it work?

Because allTogether represents an expression tree that goes somewhat like this:

FUNCTION 
  PARAMETERS: PARAM(x)
  BODY:  AND +-- NOT-EQUAL +---> PARAM(x)
          |             \---> CONSTANT(42)
          |
         AND +-- LESS-THAN +---> PARAM(x)
             |             \---> CONSTANT(100)
             |
            AND +-- GREATER-THAN +---> PARAM(x)
             |                   \---> CONSTANT(10)
             |
            TRUE

Each node in the above tree represents an Expression object in the expression tree to be compiled. The catch is that all 4 of those PARAM(x) nodes, while logically identical are in fact different instances (that help the compiler gave us by creating expression trees automatically? well, each one naturally has their own parameter instance), while for the end result to work they must be the same instance. I know this because it has bitten me in the past.

So, what needs to be done here is that you then iterate over the resulting expression tree, find each occurrence of a ParameterExpression and replace each an every one of them with the same instance. That same instance will also be the second argument used when constructing seed.

Showing how to do this will make this answer way longer than it has any right to be, but let's do it anyway. I 'm not going to comment very much, you should recognize what's going on here:

class Visitor : ExpressionVisitor
{
    private Expression param;

    public Visitor(Expression param)
    {
        this.param = param;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return param;
    }
}

And then:

var param = Expression.Parameter(typeof(int), "x");
var seed = (Expression<Func<int, bool>>)
    Expression.Lambda(Expression.Constant(true), 
    param);
var visitor = new Visitor(param);

var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Expression<Func<int, bool>>)
        Expression.Lambda(
        Expression.And(combined.Body, expr.Body), 
        param
    ),
    lambda => (Expression<Func<int, bool>>)
        // replacing all ParameterExpressions with same instance happens here
        Expression.Lambda(visitor.Visit(lambda.Body), param)
    );

Console.WriteLine(allTogether.Compile().Invoke(30)); // "True" -- works! 
Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • 2
    @siride: I 'm sorry, this answer will take long to write so I do it incrementally. Come back for the director's cut, I promise it will be worth it. – Jon Mar 17 '14 at 15:35
  • Dealing with expressions is only really worth doing if you need the result in an expression, for example, to provide it to a database query provider. If you don't, you can simply compose the functions using traditional delegate composition, which is *much* easier. You're simply trying too hard. – Servy Mar 17 '14 at 15:52
  • @Servy: True. The question doesn't say, and I went for the general case. On second thought that was not the best idea. I 'll amend. – Jon Mar 17 '14 at 15:53
  • If you really wanted to solve this as `Expression` objects, despite the fact that there is no indication in the question that that is warranted, what you would want is an implementation of a `PredicateBuilder`. See [this answer](http://stackoverflow.com/a/22407189/1159478) of mine for an implementation. – Servy Mar 17 '14 at 16:02
  • @Servy: Done, all bases covered. – Jon Mar 17 '14 at 16:02
  • See my answer here for a solution that is shorter, simpler, and clearer. You're *still* trying too hard. – Servy Mar 17 '14 at 16:03
  • 1
    @Servy: Well, once down the rocky path there's no going back. You have my upvote of course. – Jon Mar 17 '14 at 16:07
  • @Jon, I do think this is more complicated than what I need, but it's also the most helpful, informative, and complete answer. I like that this could work in an EF Linq query. +1 for detail. +10 if I could. – Andrew Mar 18 '14 at 17:04
2

In order to turn a sequence of Func<int, bool> objects into a bool you're going to need to have an integer to apply to each value. If you already know what that integer is, then you can do what Julien describes:

bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));

If you don't, then what you want to do is create a Func<int, bool> from the sequence of booleans, rather than a bool:

Func<int, bool> andResult = value => tests.All(test => test(value));
Func<int, bool> orResult = value => tests.Any(test => test(value));

We can easily generalize this into a generic function:

public static Func<T, bool> And<T>(this IEnumerable<Func<T, bool>> predicates)
{
    return value => predicates.All(p => p(value));
}
public static Func<T, bool> Or<T>(this IEnumerable<Func<T, bool>> predicates)
{
    return value => predicates.Any(p => p(value));
}

which allows you to write:

Func<int, bool> result = tests.And();
Community
  • 1
  • 1
Servy
  • 202,030
  • 26
  • 332
  • 449
0

How about this?

using System;
using System.Collections.Generic;
using System.Linq;
namespace SO7
{
    class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            LogicList<int> intBasedLogicalList = new LogicList<int>(new Func<int, bool>[] {x => x<3, x => x <5, x => x<8});
            Console.WriteLine(intBasedLogicalList.And(2));
            Console.WriteLine(intBasedLogicalList.And(4));
            Console.WriteLine(intBasedLogicalList.Or(7));
            Console.WriteLine(intBasedLogicalList.Or(8));

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }

    public class LogicList<T> : List<Func<T, bool>>
    {

        private List<Func<T,bool>> _tests;

        public LogicList(IEnumerable<Func<T, bool>> tests)
        {
            _tests = new List<Func<T, bool>>();
            foreach(var test in tests)
            {
                _tests.Add(test);
            }
        }

        public bool And(T argument){
            foreach(var test in _tests)
            {
                if (!test(argument)){
                    return false;
                }
            }
            return true;
        }

        public bool Or(T argument){
            return _tests.Any(x => x(argument));

        }

    }

}
Martin Milan
  • 6,346
  • 2
  • 32
  • 44