Deal of the Day

Home » Main » Manning Forums » 2007 » LINQ in Action

Thread: Chapter 3 needs a note

Reply to this Thread Reply to this Thread Search Forum Search Forum Back to Thread List Back to Thread List

Permlink Replies: 4 - Pages: 1 - Last Post: Mar 29, 2008 1:34 PM by: stand__sure Threads: [ Previous | Next ]
stand__sure

Posts: 3
From: Ohio
Registered: 3/24/08
Chapter 3 needs a note
Posted: Mar 24, 2008 7:58 PM
  Click to reply to this thread Reply

There is a query nuance that warrants a note. The code in Chapter 3's QueryReuse project hides an important aspect: the source portion of the query expression is immediately dereferneced (i.e. if the array pointed to by the variable is changed, the query expression does NOT update). The following code demonstrates this:

int[] numbers = { 1, 2, 3, 4, 5 };
var query = from number in numbers
select Square(number);
numbers = Array.ConvertAll(numbers, number => number + 10);
foreach (var number in query)
Console.WriteLine(number);

When the for-each loop runs, the query will look at the values 1,2,3,4,5 and not 11,12,13,14,15. The reason for this is clear when you know it is going to happen, BUT it is not clear a priori. This warrants a note to the reader to highlight the subtlety.

fabrice.marguerie


Posts: 209
From: France
Registered: 4/28/06
Re: Chapter 3 needs a note
Posted: Mar 25, 2008 7:46 AM   in response to: stand__sure in response to: stand__sure
  Click to reply to this thread Reply

Thanks for pointing this out. I'll add a note to the errata so that we consider adding a note in the next edition of the book.

Fabrice

stand__sure

Posts: 3
From: Ohio
Registered: 3/24/08
Re: Chapter 3 needs a note
Posted: Mar 25, 2008 3:19 PM   in response to: fabrice.marguerie in response to: fabrice.marguerie
  Click to reply to this thread Reply

Thanks.

One question comes to mind: short of re-assigning the query variable, is there a way to make the source parameter update?

fabrice.marguerie


Posts: 209
From: France
Registered: 4/28/06
Re: Chapter 3 needs a note
Posted: Mar 25, 2008 4:13 PM   in response to: stand__sure in response to: stand__sure
  Click to reply to this thread Reply

Let's consider what happens when you write your query:
var query = from number in numbers select Square(number);

This code is equivalent to:
var query = Enumerable.Select(numbers, number => Square(number));

This is what it gets compiled into. As can be seen more clearly with this syntax, numbers is passed as a parameter to the Select method. Internally, Select creates an iterator that keeps a reference to numbers.

When you use Array.ConvertAll, your create a new instance/collection that is completely different than the one that was used in the query (and that is known by the iterator). This explains why the query still uses the previous collection. There is no way you can change the collection used by the query. The only solution is to change the content of the collection, as we do in the book sample.

Note that if your change numbers from an array to a List<int>, you can do whatever you want with the content of the list, including changing it completely, and the query will operate on the new content.
You can test the following, for example:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = from number in numbers select Square(number);
numbers.Clear();
numbers.Add(10);
numbers.Add(20);
foreach (var number in query)
Console.WriteLine(number);


stand__sure

Posts: 3
From: Ohio
Registered: 3/24/08
Re: Chapter 3 needs a note
Posted: Mar 29, 2008 1:34 PM   in response to: fabrice.marguerie in response to: fabrice.marguerie
  Click to reply to this thread Reply

I might have been unclear.

Most would expect "reuse the query object" to mean that the actual object referenced by a token can be changed (and not just the constituent members of the object).

The following illustrates the same issue as the array case:

List<int> list1 = new List<int>();
list1.AddRange(new int[] { 1, 2, 3, 4, 5 });

Console.WriteLine("list1");
list1.ForEach(item => Console.WriteLine(item));

var query = from number in list1
select number.Square();

Console.WriteLine("Query results...");
query.ForEach(item => Console.WriteLine(item));


list1 = list1.ConvertAll(number => number + 10);

query.ForEach(item => Console.WriteLine(item));

Console.WriteLine("Updated list1");
list1.ForEach(item => Console.WriteLine(item));

Console.WriteLine("Query did NOT update...");
query.ForEach(item => Console.WriteLine(item));

// I was trying to avoid having to do this re-assignment
query = from number in list1
select number.Square();

Console.WriteLine("Query did update...");
query.ForEach(item => Console.WriteLine(item));

It is clear that the following is the case:
List<int> list2 = list1;
list1 = list1.ConvertAll(number => number + 10);
// list1 and list2 are no longer the same

But it is not clear when you are only using the token and not making an assignment -- hence, the request for the note. One would assume that you were defining something akin to a formula when writing a query (this is not the case).

My question previously was if it was possible to avoid redefining the query expression (i.e. if it was possible to make the symbol update to the new address). I understand that this is not possible... Nonetheless, I worry that a number of bugs are going to be introduced by unqualified statements about query reuse.

Legend
Gold: 300 + pts
Silver: 100 - 299 pts
Bronze: 25 - 99 pts
Manning Author
Manning Staff