views:

360

answers:

4

Hello,

Is there any way in c# .NET 2.0! to combine multiple Predicates?

Let's say I have the following code.

List<string> names = new List<string>();
names.Add("Jacob");
names.Add("Emma");
names.Add("Michael");
names.Add("Isabella");
names.Add("Ethan");
names.Add("Emily");

List<string> filteredNames = names.FindAll(StartsWithE);

static bool StartsWithE(string s)
{
    if (s.StartsWith("E"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

This gives me:

Emma
Ethan
Emily

So this is pretty cool stuff, but I know want to be able to filter using multiple predicates.

So I want to be able to say something like this:

List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI);

In order to get:

Emma
Isabella
Ethan
Emily

How can I achieve this? Currently I am just filtering the complete list twice and combining the results afterwards. But unfortunately this is quite inefficent and even more importantly I lose the original sort order, which is not acceptable in my situation.

I also need to be able to iterate over any number of filters/predicates as there can be quite a lot.

Again it needs to be a .NET 2.0 solution unfortunately I can't use a newer version of the framework

Thanks a lot.

A: 

You could create a third predicate that internally ORs the results together. I think you could do this on the fly using a lambda expression. Something like this(this is not a lambda expression as I'm not too good with that snytax):

static bool StartsWithEorI(string s)
{
    return StartsWithE(s) || StartsWithI(s);
}
AaronLS
Sure I could but like in this example there is huge number of combinations. not even thinking about combining three filters...Also again unfortunately I can use ONLY .NET 2.0
oreon
Ah that is unfortunate. I'm sure if you had anonymous delegates or lambda expressions, it would give you that "on the fly" combining power you want.
AaronLS
A: 

You could wrap the predicate method into a class and have the constructor accept an array of strings to test for:

class StartsWithPredicate
{
    private string[] _startStrings;
    public StartsWithPredicate(params string[] startStrings)
    {
        _startStrings = startStrings;
    }
    public bool StartsWith(string s)
    {
        foreach (var test in _startStrings)
        {
            if (s.StartsWith(test))
            {
                return true;
            }
        }
        return false;
    }
}

Then you can make a call like this:

List<string> filtered = names.FindAll((new StartsWithPredicate("E", "I")).StartsWith);

That way you can test for any combination of input strings without needing to extend the code base with new variations of the StartsWith method.

Fredrik Mörk
+3  A: 

How about:

public static Predicate<T> Or<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (predicate(item))
            {
                return true;
            }
        }
        return false;
    };
}

And for completeness:

public static Predicate<T> And<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (!predicate(item))
            {
                return false;
            }
        }
        return true;
    };
}

Then call it with:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI));

Another alternative would be to use multicast delegates and then split them using GetInvocationList(), then do the same thing. Then you could do:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI));

I'm not a huge fan of the latter approach though - it feels like a bit of an abuse of multicasting.

Jon Skeet
Thanks! Works perfect!
oreon
+1  A: 

In .NET 2.0, there are anonymous delegates which you can use there:

List<string> filteredNames = names.FindAll(
   delegate(string s) { return StartsWithE(s) OR StartsWithI(s); }
);

In fact, you can use it to replace your functions as well:

List<string> filteredNames = names.FindAll(
   delegate(string s) { return s.StartsWith("E") || s.StartsWith("I"); }
);
Pavel Minaev