Lev
Lev

Reputation: 287

Count() returns incorrect result on IEnumerable<T> filled using LINQ

To begin with, I have this inexplicable affection to LINQ and lambda expressions :)
So I wrote a quite straightforward code using LINQ, which is supposed to get files from a directory according to a certain name pattern, order them and accumulate until the total length of accumulated files exceeds a certain threshold:

IEnumerable<FileInfo> l_allFiles = new DirectoryInfo(l_sDirName).GetFiles().Where(l_fileInfo => ms_pattern.IsMatch(l_fileInfo.Name)).OrderBy(l_fileInfo => l_fileInfo.CreationTime);
int l_nFilesTotal = l_allFiles.Count();
if (nFilesTotal > 0)
{
    long l_nAccumulatedCmdLength = 0;
    IEnumerable<FileInfo> l_selectedFiles = l_allFiles.TakeWhile(l_fileInfo => (l_nAccumulatedCmdLength += l_fileInfo.Length) <= Settings.Default.Threshold);

    int l_nNumOfSelected = l_selectedFiles.Count();
    if (l_nNumOfSelected > 0)
    {
        l_ret = new A { Files = l_selectedFiles };
    }
}


Well, this code works fine when all found files together do not exceed the threshold.
As soon as not all found files are selected to l_selectedFiles, in most cases l_selectedFiles.Count() returns 0 even though l_selectedFiles is not empty. In rare cases, when l_selectedFiles.Count() returns the correct value, the consecutive call to Files.Count() in the A class returns 0.
To add to the mistery, the debugger always updates the value of l_nAccumulatedLength not after execution of the TakeWhile() method, but after executing the next l_selectedFiles.Count() statement. In cases that not all files are selected, the value of l_nAccumulatedLength is not always as expected...
To complete the picture, I use Microsoft Visual Studio 2010 Ultimate on Windows 7 Professional and my project targets to .NET4.0.

Can anybody give an explanation or a hint to this behavior and/or how to fix it? Because I am quite lost and cannot even imagine how exactly I debug and resolve this issue, and the community is my last hope.

Thank you all in advance for your replies and comments.

Upvotes: 2

Views: 968

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1500485

You're modifying a variable within a query:

long l_nAccumulatedCmdLength = 0;
IEnumerable<FileInfo> l_selectedFiles = l_allFiles
     .TakeWhile(l_fileInfo => (l_nAccumulatedCmdLength += l_fileInfo.Length) <=
                                   Settings.Default.Threshold);

Note the change to l_nAccumulatedCmdLength within your TakeWhile condition.

That's a really bad idea, which will end up with the sequence giving different results each time you evaluate it. Just don't do it. I strongly suspect that's the cause of the problem.

Note that this part:

To add to the mistery, the debugger always updates the value of l_nAccumulatedLength not after execution of the TakeWhile() method, but after executing the next l_selectedFiles.Count() statement.

... is very easily explained. TakeWhile doesn't iterate over the sequence - it just builds a new sequence which will be lazily evaluated.

If you want to get consistent results, use ToList... but it would be much better not to modify a variable in the query in the first place. Use Aggregate to create a sequence of Tuple<FileInfo, long> values where the long value is the "size so far" if you want.

Upvotes: 3

Related Questions