1

I have read that the LINQ is very lazy...

I have a class with a model:

class Movie
    {
        public string Title { get; set; }
        public float Rating { get; set; }

        private int year;
        public int Year
        {
            get
            {
                Console.WriteLine("Returning {0} for movie {1}", year, Title);
                return year;
            }
            set
            {
                year = value;
            }
        }
    }

And I have the following code:

            var movies = new List<Movie>
            {
                new Movie {Title = "The Dark Knight", Rating = 8.9f, Year = 2008 },
                new Movie {Title = "The King's Speech", Rating = 8.0f, Year = 2010 },
                new Movie {Title = "Casablanca", Rating = 8.5f, Year = 1942 },
                new Movie {Title = "Star Wars 5", Rating = 8.7f, Year = 1980 }
            };

            var query = movies.Where(m => m.Year > 2000);
            Console.WriteLine( query.Count( ) );
            foreach(var movie in query)
            {
                Console.WriteLine(movie.Title);
            }

And here is the output:

Returning 2008 for movie The Dark Knight
Returning 2010 for movie The King's Speech
Returning 1942 for movie Casablanca
Returning 1980 for movie Star Wars 5
2
Returning 2008 for movie The Dark Knight
The Dark Knight
Returning 2010 for movie The King's Speech
The King's Speech
Returning 1942 for movie Casablanca
Returning 1980 for movie Star Wars 5

So, the Count method does not make the query to be executed? I see this as an opposite to an optimization... So is there a logical reason behind this? (The only reasonable reason that I see for this behaviour is CQS principle)

Thanks

Buda Gavril
  • 21,409
  • 40
  • 127
  • 196
  • I am not sure I understand. What do you mean by "the Count method does not make the query to be executed?"? – Yacoub Massad Aug 31 '16 at 22:31
  • "So, the Count method does not make the query to be executed?" I don't know how you're arriving at this conclusion. Are you expecting it to now how many movies are after 2000 before analyzing all of the movies' years? I don't see how that's possible. – itsme86 Aug 31 '16 at 22:31
  • "Count method does not make the query to be executed?" Yes, it **does** cause the query to be executed. And it is executed again because of the `foreach` loop. – Dennis_E Aug 31 '16 at 22:32
  • if you add ToList() at my query, when I iterate my result, the base collection it's not iterated again – Buda Gavril Aug 31 '16 at 22:32
  • but if I execiue Count and iterate the result of the query, my base collection is iterated twice – Buda Gavril Aug 31 '16 at 22:32
  • @BudaGavril Of course, because then you are iterating the `List` you created with `ToList()` – Dennis_E Aug 31 '16 at 22:33
  • @BudaGavril That's right. That's because your `query` variable doesn't hold a result set. It holds the query which is basically the rules for generating a result set. Every time you enumerate that query, it's going to rerun the query. You can use `.ToList()` or `.ToArray()` to store the actual result set for multiple analyses to avoid multiple enumerations. This is why instead of "lazy" we use "deferred execution" because it better describes what you're experiencing. – itsme86 Aug 31 '16 at 22:33
  • Possible duplicate of [Is there a way to run the query and inspect the aggregate data without rerunning the query again?](http://stackoverflow.com/questions/32621500/is-there-a-way-to-run-the-query-and-inspect-the-aggregate-data-without-rerunning) – sstan Aug 31 '16 at 22:34
  • but my question was why does this happen because it's clear that this is not an optimization... And if the result is lazy, it can be obtained by executing Count method, why the list is enumerated again? – Buda Gavril Aug 31 '16 at 22:36
  • @itsme86 so you're saying that if I don't call a tolist or toarray method on my query, every time I'm printing the results, for example, my base collection will be iterated again ? – Buda Gavril Aug 31 '16 at 22:39
  • Because that's the way it works and it makes it very powerful. You've been told how to avoid re-enumeration by using `.ToList()`. I don't know what else you want. – itsme86 Aug 31 '16 at 22:39
  • 1
    Yes. The query itself is just a blueprint for generating results. – itsme86 Aug 31 '16 at 22:40
  • ok, thank you. "query itself is just a blueprint for generating results" made clear the behaviour – Buda Gavril Aug 31 '16 at 22:41
  • 5
    Once again, I will say what I always say when this question comes up: the value of a query is *an object that represents the question*, not *an object that represents the answer*. Why? Because every time you ask a question, *the answer can be different*. If I ask you "how many movies are in the database?" twice, the answer can be *different* each time because databases *change*. – Eric Lippert Aug 31 '16 at 22:59
  • Well, when you execute a query by calling `Count()` or `foreach`, it might generate different query. That might not be the case for Linq-to-object but for Linq to SQL or Entities, the generated request for `Count` would not retrieve data but only return the number of items. Thus, next function you call on your query might need a completly different query. **The lazy optimization is that if you don't call any function on the query, nothing would be executed and in some cases actual code would be optimized for the specific function that cause the execution of the query.** – Phil1970 Sep 01 '16 at 01:30

1 Answers1

2

Count did executed the query. those are the 4 lines before the "2". the 6 lines after is the foreach. which rerun the query.

If what puzzle you is why the expression was evaluted twice. and by

So, the Count method does not make the query to be executed?

you mean, "Evaluate the conditions and saving the result". this is not how LINQ works.

Generaly speaking, when enumerating LINQ, for each element requested (MoveNext) LINQ is calculating only the new element. This way it doesn't process data that wasn't asked for.

Rerunning the same LINQ expression will go over the elements again and do the processing all over again.

Saving & reusing the result is on you. you can easily do it using ToArray() or ToList()

Hamawi
  • 235
  • 1
  • 11