The following sections describe how to use projection operators. These operators are used to select (or “project”) contents from the source enumeration into the result.
Select
In Listing 4-3, you saw an example of defining the result of the query by using the Select operator. The signatures for the Select operator are shown here:
The Select operator is one of the projection operators because it projects the query results, making them available through an object that implements IEnumerable<T>. This object will enumerate items identified by the selector predicate. Like the Where operator, Select enumerates the source sequence and yields the result of the selector predicate. Consider the following predicate:
This predicate’s result will be a sequence of customer names (IEnumerable<string>). Now consider this example:
This predicate projects a sequence of an anonymous type, defined as a tuple of Name and City, for each customer object. With the second overload of Select, we can also provide an argument of type Integer for the predicate. This zero-based index is used to define the positional index of each item inserted in the resulting sequence.
SelectMany
Imagine that you want to select all the orders of customers from Italy. You could write the query shown in Listing 4-6 using the verbose method.
Listing 4-6: The list of orders made by Italian customers
var orders =
customers
.Where(c => c.Country == Countries.Italy)
.Select(c => c.Orders);
foreach(var item in orders) { Console.WriteLine(item); }
Because of the behavior of the Select operator, the resulting type of this query will be IEnumerable<Order[]>, where each item in the resulting sequence represents the array of orders of a single customer. In fact, the Orders property of a Customer instance is of type Order[]. The output of the code in Listing 4-6 would be the following:
To have a “flat” IEnumerable<Order> result type, we need to use the SelectMany operator:
This operator enumerates the source sequence and merges the resulting items, providing them as a single enumerable sequence. The second overload available is analogous to the equivalent overload for Select, which allows a zero-based integer index for indexing purposes. Listing 4-7 shows an example.
Listing 4-7: The flattened list of orders made by Italian customers
IEnumerable<Order> orders =
customers
.Where(c => c.Country == Countries.Italy)
.SelectMany(c => c.Orders);
Using the query expression syntax, the query in Listing 4-7 can be written with the code shown in Listing 4-8.
Listing 4-8: The flattened list of orders made by Italian customers, written with a query expression
IEnumerable<Order> orders =
from c in customers
where c.Country == Countries.Italy
from o in c.Orders
select o;
The select keyword in query expressions, for all but the initial from clause, is translated to invocations of SelectMany. In other words, every time you see a query expression with more than one from clause, you can apply this rule: the select over the first from clause is converted to an invocation of Select, and the other select commands are translated into a SelectMany call.
The third overload of SelectMany is useful whenever you need to select a custom result from the source set of sequences instead of simply merging their items, as with the two previous overloads. This overload invokes the collectionSelector predicate over the source sequence and returns the result of the resultSelector predicate, applied to each item in the collections selected by collectionSelector. In Listing 4-9, you can see an example of this method, used to extract a new anonymous type made from the Quantity and IdProduct of each order of Italian customers.
Listing 4-9: The list of Quantity and IdProduct of orders made by Italian customers
var items = customers
.Where(c => c.Country == Countries.Italy)
.SelectMany(c => c.Orders,
(c, o) => new {o.Quantity, o.IdProduct});
The query in Listing 4-9 can be written with the query expression shown in Listing 4-10.
Listing 4-10: The list of Quantity and IdProduct of orders made by Italian customers, written with a query expression
IEnumerable<Order> orders =
from c in customers
where c.Country == Countries.Italy
from o in c.Orders
select new {o.Quantity, o.IdProduct};