views:

356

answers:

1

In Linq, extension methods like Where return an IEnumerable collection, but sorting methods like OrderBy return an IOrderedEnumerable collection.

So, if you have a query that ends with OrderBy (i.e. returns an IOrderedEnumerable), you can't later append a Where method - the compiler complains about the type being passed into Where.

var query = Process.GetProcesses()
            .Where(p => p.ProcessName.Length < 10)
            .OrderBy(p => p.Id);

query = query.Where(p => p.ProcessName.Length < 5);

However, if you do it all in one query, it's fine!

var query = Process.GetProcesses()
            .Where(p => p.ProcessName.Length < 10)
            .OrderBy(p => p.Id)
            .Where(p => p.ProcessName.Length < 5);

I've looked at the assembly in Reflector to see if the compiler was re-ordering any of the operations, but it doesn't seem to have. How does this work?

+4  A: 

IOrderedEnumerable<T> extends IEnumerable<T> so you can still use any of the extension methods. The reason your first block of code didn't work is because you had effectively written:

IOrderedEnumerable<Process> query = Process.GetProcesses()
                                           .Where(p => p.ProcessName.Length < 10)
                                           .OrderBy(p => p.Id);

// Fail: can't assign an IEnumerable<Process> into a variable 
// of type IOrderedEnumerable<Process>
query = query.Where(p => p.ProcessName.Length < 5);

That fails because query.Where(...) only returns an IEnumerable<Process>, which can't be assigned to the query variable. It's not calling Where that's the problem - it's assigning the result back to the original variable. To demonstrate that, this code will work just fine:

var query = Process.GetProcesses()
                   .Where(p => p.ProcessName.Length < 10)
                   .OrderBy(p => p.Id);

// Introduce a new variable instead of trying to reuse the previous one
var query2 = query.Where(p => p.ProcessName.Length < 5);

Alternatively, you can declare query to be IEnumerable<T> to start with:

IEnumerable<Process> query = Process.GetProcesses()
                                    .Where(p => p.ProcessName.Length < 10)
                                    .OrderBy(p => p.Id);

// Fine
query = query.Where(p => p.ProcessName.Length < 5);
Jon Skeet
Yes, but what is the difference between the two examples? Why does the second one work but the first doesn't?
Graham Clark
The second one doesn't try to assign an `IEnumerable<T>` expression into a variable of type `IOrderedEnumerable<T>`.
Jon Skeet