I observe a very strange behavior, maybe could you help me to see what happen.
Here the class:
public sealed class Sudoku
{
private SudokuCell[] _grid = new SudokuCell[81];
// ctor {}
private IEnumerable<SudokuCell> Grid
{
get { return _grid; }
}
private SudokuRow[] _rows;
public IEnumerable<SudokuRow> Rows
{
get
{
if (_rows == null)
{
_rows = new SudokuRow[9];
for (int i = 0, length = 9; i < length; i++)
{
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
// Always print 9 (GOOD)
Trace.WriteLine("First Loop " + i + " : " + _rows[i].Cells.Count());
}
}
for (int i = 0; i < 9; i++)
{
// Always print 0 ! Huh 开发者_StackOverflow中文版!?
Trace.WriteLine("Second Loop " + i + " : " + _rows[i].Cells.Count());
}
return _rows;
}
}
}
public abstract class SudokuPart
{
public SudokuPart(IEnumerable<SudokuCell> cells)
{
Cells = cells;
}
public int Index
{ get; protected set; }
public IEnumerable<SudokuCell> Cells
{ get; protected set; }
}
public sealed class SudokuRow : SudokuPart
{
public SudokuRow(IEnumerable<SudokuCell> cells)
: base(cells)
{
base.Index = cells.First().Row;
}
}
Could anyone tell me why in the second loop it trace 0 instead of 9 !? I changed nothing between both loops !!!
Thanks...
This is the problem:
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
That's capturing the loop variable (i
)... within the loop, it has a sensible value, which is why you're seeing 9 matches.
However, when you count the matching values in the second loop, that single captured variable will have the value 9. Now no cell.Row
has a value of 9, so you're not getting any matches. For more information on this, see Eric Lippert's great blog post, "Closing over the loop variable considered harmful."
Three fixes:
Capture a copy of the loop variable:
int copy = i; _rows[i] = new SudokuRow(from cell in Grid where cell.Row == copy select cell);
Each iteration of the loop will get a separate copy.
Materialize the query in the loop:
_rows[i] = new SudokuRow((from cell in Grid where cell.Row == i select cell).ToList());
Or even:
_rows[i] = new SudokuRow(Grid.Where(cell => cell.Row == i).ToList());
Don't use LINQ at all! Why not just have an array of arrays to represent the grid? That's a much more natural approach, IMO.
I think Jon Skeet answer is great, but I just wanted to add a bit to it with an example of Deferred LINQ Queries. Once I saw this in action, it helped me understand a bit more about some of the nuances of this kind of code problem you ran into.
Try this code.
var numbers = new List<int> {1, 2, 3, 4, 5};
//Lets create an IEnumerable<int> with the values in our numbers list greater then 3.
var bignumbers = numbers.Where(n => n > 3);
//You may assume our variable bignumbers now contains the numbers 4 and 5
//now lets add another number to our original list that fits the criteria of our LINQ Where statement
numbers.Add(6);
foreach (var big in bignumbers) {
Console.WriteLine(big.ToString());
}
Our output from our foreach loop is going to be 4,5,6! This is because our query doesn't run until the foreach causes the enumeration of the items in our bignumbers variable.
Just something else to consider when your building lists within loops and your querying those lists outside of the loops. Your often going to get something other than what your expecting.
精彩评论