score:5

Accepted answer

Linq queries are evaluated lazily. Your statement where you assign _listTopN doesn't perform any work, it only prepares the query ; this query will only be executed when you start enumerating the results. Since you clear matches before you start enumerating the query, there is nothing in the source to enumerate...

If you want the query to be evaluated eagerly, add ToList at the end:

var _listTopN = matches.OrderByDescending(s => s.Value)
                       .Take(Settings.requiredMatch)
                       .ToList();

Alternatively, you can use ToDictionary to perform all the work in a single query:

matches = matches.OrderBy(s => s.Value)
                 .Take(Settings.requiredMatch)
                 .ToDictionary(s => s.Key, s => s.Value);

score:2

That is because _listTopN is holding on to a deferred query, lazily executed when you loop on it.

In other words, var _listTopN = ... does not evaluate the query, it returns a recipe for how to evaluate it when needed.

Since you clear the underlying source before you evaluate it, it'll "change", that is, return something other than what you wanted/expected.

The simple fix is to force evaluation, so do this:

var _listTopN = matches.OrderByDescending(s => s.Value)
    .Take(Settings.requiredMatch).ToArray();
                                 ^--------^  <-- add this

This evaluates your query and stores the result as an array that won't change, and now you can safely clear your underlying data source.

score:3

Force it to evaluate earlier:

var _listTopN = matches.OrderByDescending(s => s.Value)
    .Take(Settings.requiredMatch).ToList();

LINQ has deferred execution (as part of composition); in most cases, nothing is actually evaluated until you foreach over the data. The ToList() makes this happen earlier, i.e. before you clear the dictionary.

score:2

Your LINQ expression _listTopN is lazily evaluated which means that it doesn't evaluate its result until your foreach statement. But at this point you have already cleared matches source, so you get nothing in _listTopN also. You can force LINQ methods to evaluate their results by calling ToArray for example.

 var _listTopN = matches.OrderByDescending(s => s.Value).Take(Settings.requiredMatch)
                          .ToArray(); 

See this statement in MSDN

This method is implemented by using deferred execution

score:3

You can assign the result to matches with ToDictionary:

if (matches.Count > Settings.requiredMatch)
{
    matches = matches
        .OrderByDescending(s => s.Value)
        .Take(Settings.requiredMatch)
        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

score:1

if (matches.Count > Settings.requiredMatch)
  matches = matches.OrderByDescending(s => s.Value)
  .Take(Settings.requiredMatch)
  .ToDictionary(s => s.Key, s => s.Value);

Gets matches into the state you want simply and clearly.


Related Articles