Conversion Operators
The methods included in the conversion operator set are
AsEnumerable, ToArray, ToList,
ToDictionary, ToLookup,
OfType, and Cast. Conversion operators are
mainly defined to solve the problems and the needs that we illustrated in the
previous two sections. Sometimes you might need a stable and immutable result
from a query expression, or you might want to use a generic extension method
operator instead of a more specialized one. In the following sections, we will
describe the conversion operators in more detail.
AsEnumerable
The signature for AsEnumerable is
shown here:
The AsEnumerable operator simply returns
the source sequence as an object of type
IEnumerable<T>. This kind of “conversion on the fly” makes it
possible to call the general-purpose extension methods over source,
even if its type has specific implementations of them. You can see an example
in Listing 4-57.
Listing 4-57: A
query expression over a list of Customers converted with the AsEnumerable
operator
Customers customersList = new Customers(customers);
var expr =
from c in customersList.AsEnumerable()
where c.City == "Brescia"
select c;
foreach (var item in expr) {
Console.WriteLine(item);
}
The code in Listing 4-57
will use the standard Where operator defined for
IEnumerable<T> within System.Linq.Enumerable.
ToArray and ToList
Two other useful conversion operators are ToArray
and ToList. They convert a source sequence of type
IEnumerable<T> into an array of T (T[])
or into a generic list of T (List<T>),
respectively:
The results of these operators are snapshots of the sequence. When
they are applied inside a query expression, the result will be stable and
unchanged, even if the source sequence does change.
Listing 4-58 shows an example of using ToList.
Listing 4-58: A
query expression over an immutable list of Customers obtained by the ToList
operator
List<Customer> customersList = new List<Customer>(customers);
var expr =
from c in customersList.ToList()
where c.Country == Countries.Italy
select c;
foreach (var item in expr) {
Console.WriteLine(item);
}
These methods are also useful whenever you need to enumerate the
result of a query many times, executing the query only once for performance
reasons. Consider the sample in Listing 4-59.
It would probably be inefficient to refresh the list of products to join with
orders every time. Therefore, you can create a “copy” of the products query.
Listing 4-59: A
query expression that uses ToList to copy the result of a query over products
var productsQuery =
(from p in products
where p.Price >= 30
select p)
.ToList();
var ordersWithProducts =
from c in customers
from o in c.Orders
join p in productsQuery
on o.IdProduct equals p.IdProduct
select new { p.IdProduct, o.Quantity, p.Price,
TotalAmount = o.Quantity * p.Price};
foreach (var order in ordersWithProducts) {
Console.WriteLine(order);
}
Every time you enumerate the ordersWithProducts
expression-for instance, in a foreach block-the
productsQuery expression will not be evaluated again.
ToDictionary
Another operator in this set is the ToDictionary
extension method. It creates an instance of Dictionary<K,
T>. The keySelector predicate identifies the
key of each item. The elementSelector, if provided, is
used to extract each single item. These predicates are defined through the
available signatures:
When the method constructs the resulting dictionary, it assumes the
uniqueness of each key extracted by invoking the keySelector.
In cases of duplicate keys, an ArgumentException error
will be thrown. The key values are compared using the comparer
argument if provided or EqualityComparer<K>.Default
if not. In Listing 4-60, we use this
operator to create a dictionary of customers.
Listing 4-60: An
example of the ToDictionary operator, applied to customers
var customersDictionary =
customers
.ToDictionary(c => c.Name,
c => new {c.Name, c.City});
The first argument of the operator is the keySelector
predicate, which extracts the customer Name as the key.
The second argument is elementSelector, which creates
an anonymous type that consists of customer Name and
City properties. Here is the result of the query in
Listing 4-60:
|
Important |
Like the ToList and ToArray
operators, ToDictionary copies the source
sequence items rather than creating references to them. The ToDictionary
method in Listing 4-60 effectively
evaluates the query expression and creates the output dictionary. Therefore,
customersDictionary does not have a deferred query evaluation behavior;
it is the result produced by a statement execution.
|
ToLookup
Another conversion operator is ToLookup,
which can be used to create enumerations of type Lookup<K,
T>, whose definition follows:
Each object of this type represents a one-to-many dictionary, which
defines a tuple of keys and sequences of items, somewhat like the result of a
GroupJoin method. Here are the available signatures:
As in ToDictionary, there is a
keySelector predicate, an elementSelector predicate,
and a comparer. The sample in
Listing 4-61 demonstrates how to use this method to extract all orders
for each product.
Listing 4-61: An
example of the ToLookup operator, used to group orders by product
var ordersByProduct =
(from c in customers
from o in c.Orders
select o)
.ToLookup(o => o.IdProduct);
Console.WriteLine( "\n\nNumber of orders for Product 1: {0}\n",
ordersByProduct[1].Count());
foreach (var product in ordersByProduct) {
Console.WriteLine("Product: {0}", product.Key);
foreach(var order in product) {
Console.WriteLine(" {0}", order);
}
}
As you can see, Lookup<K, T> is
accessible through an item key (ordersByProduct[1]) or
through enumeration (the foreach loop). The following
is the output of this example:
OfType and Cast
The last two operators of this set are OfType
and Cast. The first filters the source sequence,
yielding only items of type T. It is useful in the case
of sequences with items of different types. For instance, working with an
object-oriented approach, you might have an object with a common base class and
particular specialization in derived classes:
If you provide a type T that is not
supported by any of the source items, the operator will return an empty
sequence.
The Cast operator enumerates the source
sequence and tries to yield each item, cast to type T.
In the case of failure, an InvalidCastException error
will be thrown.
Because of its signature, which accepts any IEnumerable
sequence, this method can be used to convert old nongeneric types to newer
IEnumerable<T> types. This conversion makes it possible to query
these types with LINQ even if the types are unaware of LINQ.
|
Important |
Each item returned by OfType and
Cast is a reference to the original object and not a copy.
OfType does not create a snapshot of a source; instead, it evaluates
the source every time you enumerate the operator’s result. This behavior is
different from other conversion operators.
|
|