Alfie Woodland Alfie Woodland - 2 months ago 6
C# Question

Linq - Checking for all but only if there is at least one

I'm writing a query in Linq which needs to check if one or more items in a list pass predicate

A()
, and none pass predicate
B()
. It needs to be a single query to enable Linq to SQL. So for the following lists the results should be as follows where
a
passes predicate
A()
and
b
passes predicate
B()
:

1. [ a, a ] => true
2. [ a ] => true
3. [ a, b ] => false
4. [ b, b ] => false
5. [ b ] => false
6. [ ] => false


I've tried the following, but each have certain situations where they fail:

// Fails in case 6
MyList.All(x => A(x) && !B(x));

// Fails in case 3
MyList.Where(x => !B(x)).Count(x => A(x)) > 0;

// This works, but it's not a single query anymore
MyList.All(x => A(x) && !B(x)) && Mylist.Count() > 0;


I feel like what I need here is something equivalent to all, but which returns false for an empty list. For example this would pass in all cases:

MyList.AllButReturnFalseIfListIsEmpty(x => A(x) && !B(x));


How can I achieve this?

Answer

You can do it like this:

var result = MyList
    .Select(x => B(x) ? -1 : (A(x) ? 1 : 0))
    .Aggregate(0, (p, v) => v == -1 || p == -1 ? -1 : p + v) > 0;

The query works as follows:

  • Select produces -1 when B is true; otherwise, it produces 1 if A is true and 0 when A is false
  • Aggregate checks this list of numbers from -1, 0, 1. If it sees -1, the result of the aggregation is -1. Otherwise, the result of the aggregation is sum of positive values.
  • Comparison at the end makes sure there's at least one A and no Bs.
Comments