Predicate Composition

Builds a new predicate from existing ones, so that Where, Any, All and friends can be given a composed condition without a lambda that restates it.

Func<T, bool> All<T>(params Func<T, bool>[] predicates)
Func<T, bool> Any<T>(params Func<T, bool>[] predicates)
Func<T, bool> Not<T>(Func<T, bool> predicate)

All is true when every predicate is true, Any when at least one is, Not inverts. They short-circuit like && and ||, and the predicates are evaluated left to right. All() with no predicates is always true and Any() with none always false, following the LINQ methods of the same names.

The point is reuse. Predicates that have names can be combined by name, where a lambda would have to spell out each one again:

Func<User, bool> isActive = u => u.IsActive;
Func<User, bool> isAdmin = u => u.Role == Role.Admin;
Func<User, bool> isVerified = u => u.EmailVerified;

var activeAdmins = users.Where(All(isActive, isAdmin));
var needsAttention = users.Where(Any(Not(isVerified), Not(isActive)));

// Compared to
var activeAdmins = users.Where(u => isActive(u) && isAdmin(u));

Because the result is an ordinary Func<T, bool>, it composes further: Not(All(a, b)) is "not both".

And, Or, Not as extension methods

The static forms cannot infer T from a method group, so Not(string.IsNullOrEmpty) does not compile without Not<string>(...). The same three combinators exist as extension methods on Func<T, bool> in Funcky.Extensions, where the receiver fixes the type:

Func<T, bool> And<T>(this Func<T, bool> predicate, params Func<T, bool>[] predicates)
Func<T, bool> Or<T>(this Func<T, bool> predicate, params Func<T, bool>[] predicates)
Func<T, bool> Not<T>(this Func<T, bool> predicate)

a.And(b, c) is All(a, b, c), a.Or(b, c) is Any(a, b, c) and a.Not() is Not(a), with the same short-circuiting. They read left to right, which suits longer chains:

var needsAttention = users.Where(isVerified.Not().Or(isActive.Not()));
var activeAdmins = users.Where(isActive.And(isAdmin));

And, Or, Not as extension methods

The static forms cannot infer from a method group, so does not compile without . The same three combinators exist as extension methods on in , where the receiver fixes the type:


is , is and is , with the same short-circuiting. They read left to right, which suits longer chains:

\

True and False

True and False are predicates that ignore their arguments and return a constant, with overloads for zero to four parameters:

bool True()
bool True<T1>(T1 ω1)
bool True<T1, T2>(T1 ω1, T2 ω2)
// … and the same for False

They are the predicate equivalents of Identity and NoOperation: the thing to pass when an API demands a predicate and you have no condition. SingleOrNone() is implemented as SingleOrNone(True).

// A feature flag that is always on in tests
Func<Request, bool> filter = isTestEnvironment ? True : IsAllowed;

// A default for an optional predicate parameter
IEnumerable<T> Filter<T>(IEnumerable<T> items, Func<T, bool>? predicate = null)
    => items.Where(predicate ?? True);