Is there any way in c# .NET 2.0! to combine multiple Predicates?
Let\'s say I have the following code.
List names = new List
Having used this pattern extensively with the above 'params' array method, I was intrigued by recently learning about the Multicast delegate. Since delegates inherently support a list (or multicast), you can skip the params[] pattern and just supply a single delegate to your Test() function. You will need to call GetInvokationList on the supplied Predicate<>. See this: Multicast delegate of type Func (with return value)?
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"); }
);
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);
}
I just recently came up with a solution similar to this problem, which could be also helpful. I expanded the FindAll method for lists, allowing me to stack predicates in lists as I needed:
public static class ExtensionMethods
{
public static List<T> FindAll<T> (this List<T> list, List<Predicate<T>> predicates)
{
List<T> L = new List<T> ();
foreach (T item in list)
{
bool pass = true;
foreach (Predicate<T> p in predicates)
{
if (!(p (item)))
{
pass = false;
break;
}
}
if (pass) L.Add (item);
}
return L;
}
}
It returns a list with only the items matching all given predicates. Of course it can easily be altered to OR all predicates instead of AND. But with that alone one can assemble quite a good variety of logical combinations.
Usage:
{
List<Predicate<int>> P = new List<Predicate<int>> ();
P.Add (j => j > 100);
P.Add (j => j % 5 == 0 || j % 7 == 0);
P.Add (j => j < 1000);
List<int> L = new List<int> () { 0, 1, 2, ... 999, 1000 }
List<int> result = L.FindAll (P);
// result will contain: 105, 110, 112, 115, 119, 120, ... 994, 995
}
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.
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.