3

Code:

var list=GetList()
    .where(x=>x.att01=="X")
    .where(x=>x.att02=="Y")
    .where(x=>x.att03=="Z")
    .SingleOrDefault();

Is that really equal to

var list=GetList()
    .where(x=>x.att01=="X" && x.att02=="Y" && x.att03=="Z")
    .SingleOrDefault();

I've tested it with simple arrays and it shows they are the same. The latter seems to do the job better.

  • 1
    Its the same. Its called eliminating code repetition. Put everything under one condition. – Dhrumil May 25 '15 at 06:45
  • Second approach will be faster, because of looping list only once. – Fabio May 25 '15 at 06:47
  • 1
    @Fabio False... The `Enumerable.Where` does some tricks, and auto-compacts multiple `.Where` to a single `.Where`. Look for the `WhereEnumerableIterator` class. – xanatos May 25 '15 at 06:58
  • @Carsten I don't see how side effects could change anything. The `x.att02` **won't** be executed in both cases if `x.att01 != "x"` – xanatos May 25 '15 at 06:59
  • @xanatos, what about this [http://stackoverflow.com/a/6360007/1565525](http://stackoverflow.com/a/6360007/1565525) accepted answer then? – Fabio May 25 '15 at 07:02
  • @Fabio I quote from there *it's applying the first predicate to all items first **and the result** (which is narrowed down at this point) **is used for the second predicate** and so on* – xanatos May 25 '15 at 07:06
  • @Fabio you don't need multiple iterations to apply multiple filters. Enumerable doesn't even have to do any tricks. LINQ operations are chained so that during iteration, each filter is applied to the passing *record* of the previous filter. The entire sequence only has to be iterated once – Panagiotis Kanavos May 25 '15 at 07:18
  • @Carsten I don't comprehend what you are speaking about... but you can write a small example and show it to me... – xanatos May 25 '15 at 07:41
  • @Carsten No, because you are wrong... so you can show it to me. There is no difference between the two methods... The order and then number of calls to the properties is the same. – xanatos May 25 '15 at 07:58
  • Guys, bear in mind the final function is SingleOrDefault, which implies (potentially) the entire collection to be scanned. The FirstOrDefault would behave better...However, I'm with Fabio: a multiple predicates can't be faster than an inline expression. – Mario Vernari May 25 '15 at 08:03
  • 1
    @Carsten You can play with https://ideone.com/QAQZ65 – xanatos May 25 '15 at 08:06
  • @xanatos I believe none were discussing about the equivalency, rather the computational efficiency. If your snippet's collection would contain 1M items the two way to iterate were different in terms of time. I think you agree on that! – Mario Vernari May 25 '15 at 08:09
  • @MarioVernari The first method does between 2 and 4 delegate calls for each row, the second one does 1 delegate call. Now... This difference is present, but even for one million rows, I don't think it would be more than a nuisance. He spoke expressly of *simple: add some debuging-output (Console.WriteLine, whatever) to all 3 of your attributes and then watch the order they are called* He is convinced that the "external" where will be executed completely before the second-level where will begin executing. He completely missed how enumerables work. – xanatos May 25 '15 at 08:12
  • @xanatos actually, the only result here is a mess of different things. Carsten pointed the different behavior upon a Where predicate contains something "unusual" (let's think to a query against a DB). Under this context, the two approaches aren't the same. Later, Fabio pointed out the efficiency of the latter approach. I agree: in a pure plain code, there's no substantial difference. – Mario Vernari May 25 '15 at 08:18
  • @xanatos Ahhh, Okay!...Understood. Thank you for the brilliant snippet! – Mario Vernari May 25 '15 at 08:27
  • @xanatos yeah sorry - forgot that it's only implemented on `IEnumerable` for C# :( - so you are right here – Random Dev May 25 '15 at 08:45

2 Answers2

5

Given a "standard" Enumerable.Where they are totally equivalent (from the result standpoint). The first one will be converted to the second one by the private class WhereEnumerableIterator<TSource>, to be exact by this method:

public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) {
    return new WhereEnumerableIterator<TSource>(source, CombinePredicates(this.predicate, predicate));
}

that will combine the predicates in this way:

static Func<TSource, bool> CombinePredicates<TSource>(Func<TSource, bool> predicate1, Func<TSource, bool> predicate2) {
    return x => predicate1(x) && predicate2(x);
}

See the x => predicate1(x) && predicate2(x)?

Technically the second one will be a little faster, because the first one will have some more delegate calls to do, but unless you are filtering million of rows, the time difference is negligible.

Note that, while the Enumerable.Where does funny tricks, even a non-smart .Where, like:

public static class SimpleEnumerable
{
    public static IEnumerable<TSource> SimpleWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        foreach (TSource element in source)
        {
            if (predicate(element))
            {
                yield return element;
            }
        }
    }
}

would be totally equivalent (but even slower!). See the example here: https://ideone.com/QAQZ65

xanatos
  • 109,618
  • 12
  • 197
  • 280
4

I've tested it with simple arrays and it shows they are the same.

Semantically, they both do the same. In this particular case (current overload being used), behind the scenes, the WhereEnumerableIterator<TSource> will output a single enumerator with chained predicates:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, 
                                                  Func<TSource, bool> predicate) 
{
    // This is the important line
    if (source is Iterator<TSource>) 
        return ((Iterator<TSource>)source).Where(predicate);
    if (source is TSource[]) 
        return new WhereArrayIterator<TSource>((TSource[])source, predicate);
    if (source is List<TSource>) 
        return new WhereListIterator<TSource>((List<TSource>)source, predicate);
    return new WhereEnumerableIterator<TSource>(source, predicate);
}

The IEnumerable<TSource> is actually a previous WhereEnumerableIterator<TSource> which was provided from the Where calls. Ultimately, it will end up merging the predicates:

public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) 
{
     return new WhereEnumerableIterator<TSource>(source,
                                                 CombinePredicates(this.predicate, 
                                                                   predicate));
}

static Func<TSource, bool> CombinePredicates<TSource>(Func<TSource, bool> predicate1,
                                                      Func<TSource, bool> predicate2) 
{
    return x => predicate1(x) && predicate2(x);
}

But, SingleOrDefault itself has an overload taking a Func<TSource, bool> which would eliminate the need to call Where at all:

var list = GetList().SingleOrDefault(x => x.att01 == "X" && 
                                          x.att02 == "Y" && 
                                          x.att03 == "z");
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321