Jack_of_no_trades
Jack_of_no_trades

Reputation: 362

Force LINQ to execute again?

I have got this code from a c# book:

int minSize = 10000;
var bigFiles = from file in GetAllFilesInDirectory(@"c:\")
 where new FileInfo(file).Length > minSize
 select file;

var filesOver10k = bigFiles.ToArray();

minSize = 100000;
var filesOver100k = bigFiles.ToArray();

minSize = 1000000;
var filesOver1MB = bigFiles.ToArray();

minSize = 10000000;
var filesOver10MB = bigFiles.ToArray();

The author says it will re-evaluate the query every time ToArray() is called. I want to do something similar. I need to query how many times each letter of the alphabet is used in a book; currently I'm using something like this:

string alphabet="abcdefghijklmnopqrstuvwxyz";

foreach(char a in alphabet)
{
 var stat_letter=book2.book.Sum(b=>b.chapter.Sum(l=>l.line.Sum(w=>w.word.ToLower().Count(c=>c.Equals(a)))));
 Console.WriteLine(a + ":" + stat_letter.ToString() );
}

The output I get:

a: 31278
b: 6263
c: 14561
[...]

I wanted to change it to work much like the book example:

char q = 'a';
var stat_letter = book2.book.Sum(b=>b.chapter.Sum(l=>l.line.Sum(w=>w.word.ToLower().Count(c=>c.Equals(q)))));

string alphabet="abcdefghijklmnopqrstuvwxyz";

foreach(char a in alphabet)
{
 q=a;
 Console.WriteLine(q + ":" + stat_letter.ToString() );
}

The output I get from this:

a: 31278
b: 31278
c: 31278
[...]

It looks like the query doesn't re-evaluate in this case. Is there any way to force it? Actually my motivation is to check if it would speed up the program execution, so if you think it would not, that is also something I'd like to know...

Upvotes: 3

Views: 324

Answers (3)

Servy
Servy

Reputation: 203844

Sum returns an int (using the overload you chose) not an IEnumerable. That means it's evaluated immediately, because it needs that int. The value of an int cannot be deferred.

While there are a few ways of re-structuring your code to defer execution, what I would prefer to do is to create a method (or a lambda, same concept really) that computes the value you want given the input you have, instead of mutating a variable that a query relies on.

Func<char, int> computeLetterCount = letter => book2.book.Sum(
    b=>b.chapter.Sum(
        l=>l.line.Sum(
            w=>w.word.ToLower().Count(c=>c.Equals(letter)))));

You can now write:

foreach(char a in alphabet)
{
     Console.WriteLine(a + ":" + computeLetterCount(a) );
}

Upvotes: 7

evanmcdonnal
evanmcdonnal

Reputation: 48114

It's not that the query doesn't re-evaluate, it's that LINQ uses deferred execution where possible. In the case you're looking at it's using deferred execution and each time you call ToArray() the query executes on the collection at that point in time. In your example, one or more of the query operators (Sum for sure does) force execution so it doesn't matter if you keep trying to run it over and over again, the query has already ran against the collection at that point in time and the actual result is whats stored in that variable. Basically, just use your working code because it's the idiomatic way to do it.

Upvotes: -1

Ulugbek Umirov
Ulugbek Umirov

Reputation: 12807

You calculated the value and saved it in variable. Of course it won't be reevaluated. But you can do the following:

Func<char,int> stat_letter_func = q => book2.book.Sum(b=>b.chapter.Sum(l=>l.line.Sum(w=>w.word.ToLower().Count(c=>c.Equals(q)))));

string alphabet="abcdefghijklmnopqrstuvwxyz";

foreach(char a in alphabet)
{
    Console.WriteLine(a + ":" + stat_letter_func(a).ToString() );
}

Upvotes: 2

Related Questions