0

I have a problem with looping through a set of XElements. The behavior is not what I expected.

A short re-written version of my code, so you can easily get my problem (console app in C#)

IEnumerable<XElement> q = from c in xml.Descendants(aw + "wd") 
                          where (....) 
                          select c;    
...

//--------------------------------------------------------------------    
IEnumerable<XElement> currRow = q.OrderBy(yyy => (int)yyy.Attribute("t"));

int xValue = 10;

currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);
xValue = 20; 
//Here, the currRow gets a new value automatically. I don't want this!


//--------------------------------------------------------------------
//This is want i want to acheive: 

IEnumerable<XElement> currRow = q.OrderBy(yyy => (int)yyy.Attribute("t")); 

int xValue = 10;

currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);
//do somthing with currRow

xValue = 20; 
currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);
//do somthing else with currRow

xValue = 30; 
currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);
// etc....

Any ideas?

Chaitanya K
  • 1,827
  • 4
  • 32
  • 67
  • 2
    If you dont want this, then assign result in new variables, not again in currRow – Yograj Gupta Nov 01 '12 at 10:05
  • 1
    You seem to be using `currRow` interchangeably as either a single row *or* an `IEnumerable`. I'm also not sure what you mean when you say "Here, the currRow gets a new value automatically. I don't want this!" You've just explicitly *set* a new value for `currRow`; what behaviour *did* you want? – Dan Puzey Nov 01 '12 at 10:15

5 Answers5

1

every time you call .where and assign the result to currRow you are limiting the set.

so the first line

 currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);

will limit to all elements where t < 10. When you then call

 xValue = 20;
 currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);

you will get the exact same result because all elements i currRow has a value of t less than 10 due to the first call to Where and subsequent assignment to currRow

you could do this

   const int stepSize = 10;
   var iterations = 3
    for(var i = 0;i<iterations;++i){
         var curr = currRow.Where(yyy => (int)YYY.Attribute("t") >= i * stepSize 
                                       && (int)YYY.Attribute("t") < (i+1) * stepSize);
         //do sometihng with curr
    }

EDIT after comment

the where is evaluated when you use the enumerator

xValue = 10;
currRow = currRow.Where(yyy => (int)YYY.Attribute("t") > xValue);

foreach(var elem in currRow){
    //this iterates all elements with t < 10;
}

xValue = 20; 
foreach(var elem in currRow){ //notice that currRow hasn't been reassigned
    //this iterates all elements with t < 20;
}

you have to options to avoid this behavior which is by design

either you can refrain from reassigning xValue or you can execute the query like this

var list = currRow.Where(yyy => (int)YYY.Attribute("t") > xValue).ToList();

notice the .ToList() in the end.

Rune FS
  • 21,497
  • 7
  • 62
  • 96
  • Sorry for misleading. The above code is just a simplified and re-written version of my code, so the functionality is not the same as in my code. I now realize that my example was bad. My main question is; why does currRow change value as soon as i assign a new value to xValue and how can i stop this? i want the "normal" behavior. Assign a value to xValue, use it in the where-expressen and get a new set of data in currRow. That's it :) – user1789252 Nov 01 '12 at 10:39
  • Thanx... toList() was the best answer. I thought about the solution with not tampering with xValue, but that would've created a much uglier code + i want to solve the problem rather than going around them! :) – user1789252 Nov 01 '12 at 15:05
  • @user1789252 both have pros and cons. Eg. With ToList you are going to iterate the full source and then all the elements you need from the result. Where best case for not reassigning xValue is that you iterate n elements of the source (where n is the number of elements you need) worst but also most likely you'll iterate the full source but that's still iterations better than when using ToList – Rune FS Nov 01 '12 at 16:02
0
IEnumerable<XElement> currRow = q.OrderBy(yyy => (int)yyy.Attribute("t")); 

int xValue = 10;    

var abc = currRow.Where(yyy => (int)yyy.Attribute("t") < xValue);
//Do something with abc , abc is IEnumerable<XElement> list;
Đức Bùi
  • 517
  • 1
  • 6
  • 22
  • The above works, but is not applicable in my solution, since i use a whil-loop with several where/order statements. My Ienumrable variable gets screwed up every time i assign a value to xValue. – user1789252 Nov 01 '12 at 10:29
0
currRow.Where(...)

Will create a query that will be executed when used.
If you want the result directly (and omitting any references/query to be screwed up) convert it to a list.

currRow.Where(...).ToList()

The reason why you should to this is because the value you use in your where statement isn't seen as a constant within the query but a reference to the variable you used. If you change the variable, you possibly changed the outcome of the query.
If you call .ToList() the query is executed with the value of the variable on that moment. The result is gathered and is 'permanent' in such way that the query which was made to create the result won't be influencing the result any more when the value of the variable used in the query changes.

Mixxiphoid
  • 1,044
  • 6
  • 26
  • 46
  • Wow.. thanx! That did the trick! Perfect explanation aswell! Kudos! – user1789252 Nov 01 '12 at 12:21
  • THe last sentence has nothing to do with the behabvior. It's not changing the type that's the key it's executing the query. Both types implement IEnumerable and you could have a type that inherited List that was also lazily evaluated – Rune FS Nov 01 '12 at 13:02
  • @Rune FS you're correct, I said it wrong, although I wanted to make clear that .ToList() would execute the query. I removed that sentence. – Mixxiphoid Nov 01 '12 at 13:29
  • " where statement isn't seen as a constant within the query but a reference to the variable you used. If you change the variable, you possibly changed the outcome of the query." "The result is gathered and is 'permanent'" Above is actually what my Question was all about. Why is the data (currRow) changing when i do not want it to change! .ToList() was one way to go as mentioned. – user1789252 Nov 01 '12 at 15:03
0

Looks like your xValue is captured into lambda expression of where statement. Why not simply provide constant value?

currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < 20);
//do somthing with currRow
currRow = currRow.Where(yyy => (int)yyy.Attribute("t") < 10);
//do somthing with currRow

Also do not start with minimal filter value < 10. When you assign result to currRow variable other filters will have no effect, because result already will be less than 20 and less than 30.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • I cannot use constants. My code above is a very simplified version of the actual code. I use a While-loop where the xValue changes in every loop. The xValues above are all fictional :) – user1789252 Nov 01 '12 at 10:16
0

I don't have VS on my machine at the moment to test, but it's likely due to lazy loading.

When you use xValue in the LINQ expression it's passed by reference instead of by value. This means that until you enumerate the LINQ expression, whenever you change xValue you're also unwittingly changing the LINQ expression.

This post (Are values in LINQ expressions passed by reference?) seems to support my theory. Try changing your select statement to the following, it will probably give you the behaviour you're expecting:

currRow = currRow.Where( yyy => ( int ) yyy.Attribute( "t" ) < xValue ).Select( x => x );
Community
  • 1
  • 1
  • Thanx... You seem to be right, as other stated above. It has something to do with reference instead of value. Havn't tested your solution yet, but .toList() worked just fine. – user1789252 Nov 01 '12 at 15:08