4

Is there any difference in the below two approaches? I am getting same output but I am trying to understand which is right and effective

approach 1:

Product product12 = products.Where(p => p.ProductID == 12).First();

approach 2:

Product prod12 = products.First(p => p.ProductID == 12);
hanuma
  • 57
  • 1
  • 9
  • Possible duplicate: http://stackoverflow.com/questions/8663897/why-is-linq-wherepredicate-first-faster-than-firstpredicate?rq=1 – Eris Jul 26 '15 at 05:01

4 Answers4

8

(I assume that you are using Linq to .Net)
Firstly let's look at their source codes:

Here is Where():

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) 
        throw Error.ArgumentNull("source");
    if (predicate == null) 
        throw Error.ArgumentNull("predicate");
    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);
}

And here is First()

   public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
            if (source == null) throw Error.ArgumentNull("source");
            if (predicate == null) throw Error.ArgumentNull("predicate");
            foreach (TSource element in source) {
                if (predicate(element)) return element;
            }
            throw Error.NoMatch();
        }

And let's find what does each code do:

  1. products.First(p => p.ProductID == 12);

Based on source code of the First() we can say, First() will iterate over collection, and will stop iteration when it finds first item in a collection which mets the criteria.

  1. products.Where(p => p.ProductID == 12).First();

First it will create iterator in Where method where elements meet the criteria. Then, again it will add get the first elemnt to that iterator. And, again will returm the first element as soon as it finds it.

As an additional note, LINQ uses deferred execution in some methods. And it has some relationship with the result of your question.

Deferred execution is a pattern of the execution model by which the CLR ensures a value will be extracted only when it is required from the IEnumerable-based information source. When any Linq operator uses the deferred execution, the CLR encapsulates the related information, such as the original sequence, predicate, or selector (if any), into an iterator, which will be used when the information is extracted from the original sequence using ToList method or ForEachmethod or manually using the underlying GetEnumerator and MoveNext methods in C#.

And, the question is. Which one is faster?


The main point is that, Where().First is quicker than First. in case of List and Array. Otherwise, First() will be faster. Here is the detailed exlpanation in @Akash Kava answer.

Let's pay an attention to Where() implementation. It will return WhereListIterator() if your collection is List, but First() will just iterate over source. And in my opinion they have made some speed up in the implementation of WhereListIterator. And after this we are calling First() method which takes no predicate as input and only will iterate on filtered collection.

Also, as I understand, The Where iterator avoids indirect virtual table call, but calls iterator methods directly. And, this is the reason of this speed-up.

Community
  • 1
  • 1
Farhad Jabiyev
  • 26,014
  • 8
  • 72
  • 98
  • Where+First will not iterate over the whole collection. – MarcinJuraszek Jul 26 '15 at 05:28
  • @AkashKava Very interesting. Thing are getting more interesting, when you changed `x.Size < 20` to `x.Size == 20`. Then, `Where().First()` will be faster. – Farhad Jabiyev Jul 26 '15 at 06:37
  • @AkashKava Additional. if you chnage it to `x.Size > 20`, then `Where().First()` will be really faster. Here is fiddle: https://dotnetfiddle.net/qSLSrI – Farhad Jabiyev Jul 26 '15 at 06:41
  • I got 110 milliseconds for both, I think there is no point which one is faster, it has also something to do with CPU scheduling, if CPU is overloaded, it will be slower, there is no definite answer to this, based on conditions answer varies about which one is fast. – Akash Kava Jul 26 '15 at 06:54
  • @AkashKava I agree with you in some points. But, to tell the truth `Where().First()` executes faster than `First()` generally. I have tested this before with another tests, and as I remember `Where().First()` was faster in almost 90% of tests. But, again I can't tell the reason of why `First()` executes faster when the filter is `<`. And I want to find it out. – Farhad Jabiyev Jul 26 '15 at 07:01
  • @AkashKava From my point of view, I have found it the reason. `<` case is exceptional, because the result is the first element in collection. Try to add, beofre queries: ` items.Reverse();` – Farhad Jabiyev Jul 26 '15 at 07:07
  • No, if you see I have called items.OrderBy( x=> new Guid()) , this randomizes the collection. See my new fiddle, in all cases First is faster than Where First. – Akash Kava Jul 26 '15 at 07:09
  • Thank you @FarhadJabiyev for such a nice explanation. Its very informative. – hanuma Jul 26 '15 at 15:26
  • @FarhadJabiyev, try this example, First is always faster then Where First, https://dotnetfiddle.net/OrUUSG – Akash Kava Jul 27 '15 at 18:15
1

I just ran a test, you can see results here on .Net fiddle, https://dotnetfiddle.net/3lCqsR

True answer is, which is faster depends on the type of filter that is applied and the type of collection that is iterated over.

    Func<Item,bool> filter = x => x.Size == 200;

    Run("First", () => items.First( filter ));
    Run("Where First", () => items.Where( filter ).First());

88 milliseconds for First
69 milliseconds for Where First


    filter = x => x.Size < 200;

    Run("First", () => items.First( filter ));
    Run("Where First", () => items.Where( filter ).First());

2 milliseconds for First
4 milliseconds for Where First


    filter = x => x.Size > 200;

    Run("First", () => items.First( filter ));
    Run("Where First", () => items.Where( filter ).First());

88 milliseconds for First
71 milliseconds for Where First

So there is no definite answer.

Another test, https://dotnetfiddle.net/k11nX6 , all the time, First is faster than Where First.

UPDATE

After analyzing List class, it is found that Where().First() is only faster than First(), in case of List and Array only, it does not apply to IQueryable or any other form of Enumerable. The reason is, Enumerator used in List is not cached per thread. List always creates new Enumerator, so First() uses List's Enumerator (iterator).

WhereListIterator used in Where() for List, does not create new Enumerator, instead it caches current Iterator in thread. This makes Where().First() run faster.

However, this operation of caching WhereListIterator has some cost, so for smaller collection and some conditions that yield smaller results, Where().First() is slower.

If you look at this example, First beats Where First everytime.

https://dotnetfiddle.net/OrUUSG

iappwebdev
  • 5,880
  • 1
  • 30
  • 47
Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • To tell the truth, I have reruned it. And again `First` is only faster when filter is `filter = x => x.Size < n;`. Try to add `items.Reverse()`. – Farhad Jabiyev Jul 26 '15 at 07:09
  • Did you try https://dotnetfiddle.net/k11nX6 ? Also see that there is a random order by, so items are not ordered. – Akash Kava Jul 26 '15 at 07:10
  • Yes, so as a result. We can syat that, **generally** `Where().First()` will be faster. Because, The Where iterator avoids indirect virtual table call, but calls iterator methods directly. But, of course we **can find** tests when `First()` is faster. But, Iwould be very happy, if someone has a time and can search and tell us the concertee reasons behind these. – Farhad Jabiyev Jul 26 '15 at 07:19
  • 1
    @FarhadJabiyev, new update, Where().First() is only faster for List and Array, it is not generally faster for other Enumerables. See my update – Akash Kava Jul 26 '15 at 07:45
  • Really very thanks for valuable information. I will add link to your answer in my answer. – Farhad Jabiyev Jul 26 '15 at 07:47
  • @Akash kava, Thanks for the nice analysis. I will try some more combinations with fiddle. – hanuma Jul 26 '15 at 15:31
0

They are functionally equivalent and equally effective. LINQ builds a query which is not evaluated until the result is enumerated, e.g. a foreach loop or in this case the First() operation.

So both will evaluate the items in the original order until it finds the first instance with ProductID == 12 and returns that instance if found (and throws an exception if not).

That said the latter approach is my preferred method as it is more succinct and, generally, easier to read.

clovecooks
  • 26
  • 3
0

I think your question is more like:

  1. Will Where iterate the whole collection before getting to First?
  2. Will First do the same as well?

I've written my own implementation to Where and First.
This example isn't the exact .net implementation, but it works in the same concept.

public static class Extensions
{
    public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> collection, Func<T,bool> condition)
    {
        foreach(var item in collection)
        {
            if(condition(item))
            {
                yield return item;
            }
        }
    }

    public static T MyFirst<T>(this IEnumerable<T> collection, Func<T,bool> condition)
    {
        foreach (var item in collection)
        {
            if (condition(item))
            {
                return item;
            }
        }

        throw new InvalidOperationException("No element found");
    }
}

Execute this code:

List<int> myList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var five = myList.MyWhere(i => i < 6).MyFirst(i => i == 5);

Place a debugger breakpoint inside the MyWhere and MyFirst foreach and start understanding what happens when you work with iterators using Linq To Objects.

Amir Popovich
  • 29,350
  • 9
  • 53
  • 99