views:

54

answers:

2

I have a Linq query that looks something like this:

var myPosse = from p1 in people
             select p1;
label1.Text = "All my peeps:" + Environment.NewLine;
foreach (Person p in myPosse)
{
    this.label1.Text += p.ToString() + Environment.NewLine;
}

This gives me good results.

But when I do something like this:

var myPosse = from p1 in people
             select p1;
label1.Text = "All my peeps:" + Environment.NewLine;
people.Add(new Person{FirstName="Don", LastName="Cash"});
foreach (Person p in myPosse)
{
    this.label1.Text += p.ToString() + Environment.NewLine;
}

I have the 'extra' guy in there! How the heck is this happening? My Linq variable is set before the extra guy is added.

+7  A: 

This is because of deferred execution, a major feature of Linq.

What is stored in var is not actually a result set. It is actually the potential to run a query. The query isn't run when the value is assigned to a variable. It runs only as needed and does so little by little.

This is a huge part of Linq and is there for efficiency's sake. Deferred execution saves lots of time and resources b/c it is possible to abort the query early and thus, we've wasted time and memory if we don't need the tail end. Also, if the result set is mega-huge, it is inefficient. (Think of slurping a file versus streaming it in say, perl or PHP).

You can't really force immediate execution, but here's a trick to approximate that.

var myPosse = from p1 in people
             select p1;
List<Person> theTeam = myPosse.ToList();
label1.Text = "All my peeps:" + Environment.NewLine;
people.Add(new Person{FirstName="Don", LastName="Cash"});
foreach (Person p in theTeam)
{
    this.label1.Text += p.ToString() + Environment.NewLine;
}

Note the "ToList()" method. In this situation your Linq query executes in full at the .ToList() moment. Your original list is preserved in theTeam while you have your 'extra' guy in the people IEnumerable.

Rap
+3  A: 

When you iterate through the results of most (but not all) LINQ extension methods, the results aren't actually computed until you use them. This is due to deferred execution.

What's happening is LINQ creates an IEnumerable<Person> that will iterate through your "people" collection. However, this isn't a copy of your collection - it will enumerate as requested.

When you do your "foreach" loop, later, it actually starts iterating through the collection of people - using people as it is at that point. This means you'll get your extra person.

You can avoid this by using a LINQ extension that will create a copy of your data:

var myPosse = (from p1 in people
         select p1).ToList();  // This makes a copy in a new list...
label1.Text = "All my peeps:" + Environment.NewLine;
people.Add(new Person{FirstName="Don", LastName="Cash"});
foreach (Person p in myPosse)
{
    this.label1.Text += p.ToString() + Environment.NewLine;
    // Don Cash won't show up now, since it wasn't in the original list when the copy was made
}
Reed Copsey
wouldn't be better if you call .Count() on on myPosse just after the query ? it would be better than converting it to list :var myPosse = (from p1 in people select p1).ToList();myPosse.Count();
Yassir