Tải bản đầy đủ (.pdf) (52 trang)

Apress pro LINQ Language Integrated Query in C# 2008 phần 3 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (873.86 KB, 52 trang )

108
CHAPTER 4
■ DEFERRED OPERATORS
In the preceding code, notice I am enumerating through an outer sequence named outerSequence,
where each element is an object implementing IGrouping containing the key, and a sequence of
EmployeeOptionEntry elements having that same key.
Here are the results:
Option records for employee: 1
id=1 : optionsCount=2 : dateAwarded=12/31/1999
Option records for employee: 2
id=2 : optionsCount=10000 : dateAwarded=6/30/1992
id=2 : optionsCount=10000 : dateAwarded=1/1/1994
id=2 : optionsCount=10000 : dateAwarded=4/1/2003
Option records for employee: 3
id=3 : optionsCount=5000 : dateAwarded=9/30/1997
id=3 : optionsCount=7500 : dateAwarded=9/30/1998
id=3 : optionsCount=7500 : dateAwarded=9/30/1998
Option records for employee: 4
id=4 : optionsCount=1500 : dateAwarded=12/31/1997
Option records for employee: 101
id=101 : optionsCount=2 : dateAwarded=12/31/1998
For an example of the second GroupBy prototype, let’s assume I know that any employee whose
id is less than 100 is considered a founder of the company. Those with an id of 100 or greater are not
considered founders. My task is to list all option records grouped by the option record’s employee
founder status. All founders’ option records will be grouped together, and all nonfounders’ option
records will be grouped together.
Now, I need an equality comparer that can handle this key comparison for me. My equality
comparer must implement the IEqualityComparer interface. Before examining my comparer, let’s
take a look at the interface.
The iIEqualityComparer<T> Interface
interface IEqualityComparer<T> {


bool Equals(T x, T y);
int GetHashCode(T x);
}
This interface requires me to implement two methods, Equals and GetHashCode. The Equals
method is passed two objects of the same type T and returns true if the two objects are considered
to be equal or false otherwise. The GetHashCode method is passed a single object and returns a hash
code of type int for that object.
A hash code is a numerical value, typically mathematically calculated based on some portion
of the data in an object, known as the key, for the purpose of uniquely identifying the object. That
calculated hash code functions as the index into some data structure to store that object and find it
at a later time. Since it is typical for multiple keys to produce the same hash code, thereby making the
hash code truly less than unique, it is also necessary to be able to determine if two keys are equal.
This is the purpose of the Equals method.
Here is my class implementing the IEqualityComparer interface.
Rattz_789-3.book Page 108 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
109
A Class Implementing the IEqualityComparer Interface for My Second GroupBy Example
public class MyFounderNumberComparer : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
return(isFounder(x) == isFounder(y));
}
public int GetHashCode(int i)
{
int f = 1;
int nf = 100;
return (isFounder(i) ? f.GetHashCode() : nf.GetHashCode());
}

public bool isFounder(int id)
{
return(id < 100);
}
}
In addition to the methods required by the interface, I have added a method, isFounder, to
determine if an employee is a founder based on our definition. This just makes the code a little easier
to understand. I have made that method public so that I can call it from outside the interface, which
you will see me do in my example.
My equality comparer is going to consider any integer less than 100 as representing a founder,
and if two integers signify either both founders or both nonfounders, they are considered equal. For
the purposes of producing a hash code, I return a hash code of 1 for a founder and 100 for a nonfounder
so that all founders end up in the same group, and all nonfounders end up in another group.
My GroupBy example code is in Listing 4-31.
Listing 4-31. An Example of the Second GroupBy Prototype
MyFounderNumberComparer comp = new MyFounderNumberComparer();
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();
IEnumerable<IGrouping<int, EmployeeOptionEntry>> opts = empOptions
.GroupBy(o => o.id, comp);
// First enumerate through the sequence of IGroupings.
foreach (IGrouping<int, EmployeeOptionEntry> keyGroup in opts)
{
Console.WriteLine("Option records for: " +
(comp.isFounder(keyGroup.Key) ? "founder" : "non-founder"));
// Now enumerate through the grouping's sequence of EmployeeOptionEntry elements.
foreach (EmployeeOptionEntry element in keyGroup)
Console.WriteLine("id={0} : optionsCount={1} : dateAwarded={2:d}",
element.id, element.optionsCount, element.dateAwarded);
}
Rattz_789-3.book Page 109 Tuesday, October 16, 2007 2:21 PM

110
CHAPTER 4
■ DEFERRED OPERATORS
In the example, I instantiate my equality comparer object ahead of time, as opposed to doing it
in the call to the GroupBy method, so that I can use it to call the isFounder method in the foreach loop.
Here are the results from this code:
Option records for: founder
id=1 : optionsCount=2 : dateAwarded=12/31/1999
id=2 : optionsCount=10000 : dateAwarded=6/30/1992
id=2 : optionsCount=10000 : dateAwarded=1/1/1994
id=3 : optionsCount=5000 : dateAwarded=9/30/1997
id=2 : optionsCount=10000 : dateAwarded=4/1/2003
id=3 : optionsCount=7500 : dateAwarded=9/30/1998
id=3 : optionsCount=7500 : dateAwarded=9/30/1998
id=4 : optionsCount=1500 : dateAwarded=12/31/1997
Option records for: non-founder
id=101 : optionsCount=2 : dateAwarded=12/31/1998
As you can see, all employee options records for an employee whose id is less than 100 are
grouped with the founders. Otherwise, they are grouped with the nonfounders.
For an example of the third GroupBy prototype, we’ll assume we are only interested in getting the
dates that the options were awarded for each employee. This code will be very similar to the example
for the first prototype.
So in Listing 4-32, instead of returning a sequence of groupings of EmployeeOptionEntry objects,
I will have groupings of dates.
Listing 4-32. An Example of the Third GroupBy Prototype
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();
IEnumerable<IGrouping<int, DateTime>> opts = empOptions
.GroupBy(o => o.id, e => e.dateAwarded);
// First enumerate through the sequence of IGroupings.
foreach (IGrouping<int, DateTime> keyGroup in opts)

{
Console.WriteLine("Option records for employee: " + keyGroup.Key);
// Now enumerate through the grouping's sequence of DateTime elements.
foreach (DateTime date in keyGroup)
Console.WriteLine(date.ToShortDateString());
}
Notice that in the call to the GroupBy operator, elementSelector, the second argument, is just
returning the dateAwarded member. Because I am returning a DateTime, my IGrouping is now for a
type of DateTime, instead of EmployeeOptionEntry.
Just as you would expect, I now have the award dates of the options grouped by employee:
Option records for employee: 1
12/31/1999
Option records for employee: 2
6/30/1992
1/1/1994
4/1/2003
Option records for employee: 3
9/30/1997
Rattz_789-3.book Page 110 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
111
9/30/1998
9/30/1998
Option records for employee: 4
12/31/1997
Option records for employee: 101
12/31/1998
For the fourth and final prototype, I need to use an elementSelector method and a comparer
object, so I will use a combination of the examples for prototypes two and three. I want to group the
dates of awarded options by whether they were awarded to a founding employee or not, where a

founding employee is one whose id is less than 100. That code is in Listing 4-33.
Listing 4-33. An Example of the Fourth GroupBy Prototype
MyFounderNumberComparer comp = new MyFounderNumberComparer();
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();
IEnumerable<IGrouping<int, DateTime>> opts = empOptions
.GroupBy(o => o.id, o => o.dateAwarded, comp);
// First enumerate through the sequence of IGroupings.
foreach (IGrouping<int, DateTime> keyGroup in opts)
{
Console.WriteLine("Option records for: " +
(comp.isFounder(keyGroup.Key) ? "founder" : "non-founder"));
// Now enumerate through the grouping's sequence of EmployeeOptionEntry elements.
foreach (DateTime date in keyGroup)
Console.WriteLine(date.ToShortDateString());
}
In the output, we should see just dates grouped by founders and nonfounders:
Option records for: founder
12/31/1999
6/30/1992
1/1/1994
9/30/1997
4/1/2003
9/30/1998
9/30/1998
12/31/1997
Option records for: non-founder
12/31/1998
Set
The set operators are used to perform mathematical set-type operations on sequences.
■Tip The prototypes of the set operators that are covered in this chapter do not work properly for DataSets. For

use with DataSets please use the prototypes that are covered in Chapter 10.
Rattz_789-3.book Page 111 Tuesday, October 16, 2007 2:21 PM
112
CHAPTER 4
■ DEFERRED OPERATORS
Distinct
The Distinct operator removes duplicate elements from an input sequence.
Prototypes
The Distinct operator has one prototype I will cover.
The Distinct Prototype
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source);
This operator returns an object that, when enumerated, enumerates the elements of the input
sequence named source and yields any element that is not equal to a previously yielded element. An
element is determined to be equal to another element using their GetHashCode and Equals methods.
Isn’t it fortuitous that I just covered how and why the GetHashCode and Equals methods are used?
Exceptions
ArgumentNullException is thrown if the source argument is null.
Examples
For this example, I am going to first display the count of the presidents array, next I will concatenate
the presidents array with itself, display the count of the resulting concatenated sequence, then call
the Distinct operator on that concatenated sequence, and finally display the count of the distinct
sequence which should be the same as the initial presidents array.
To determine the count of the two generated sequences, I will use the Count Standard Query
Operator. Since it is a nondeferred operator, I will not cover it in this chapter. I will cover it in the next
chapter, though. For now, just be aware that it returns the count of the sequence on which it is called.
The code is in Listing 4-34.
Listing 4-34. An Example of the Distinct Operator
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",

"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
// Display the count of the presidents array.
Console.WriteLine("presidents count: " + presidents.Count());
// Concatenate presidents with itself. Now each element should
// be in the sequence twice.
IEnumerable<string> presidentsWithDupes = presidents.Concat(presidents);
// Display the count of the concatenated sequence.
Console.WriteLine("presidentsWithDupes count: " + presidentsWithDupes.Count());
// Eliminate the duplicates and display the count.
IEnumerable<string> presidentsDistinct = presidentsWithDupes.Distinct();
Console.WriteLine("presidentsDistinct count: " + presidentsDistinct.Count());
Rattz_789-3.book Page 112 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
113
If this works as I expect, the count of the elements in the presidentsDistinct sequence should
equal the count of the elements in the presidents sequence. Will our results indicate success?
presidents count: 37
presidentsWithDupes count: 74
presidentsDistinct count: 37
Yes, they do!
Union
The Union operator returns a sequence of the set union of two source sequences.
Prototypes
This operator has one prototype I will cover.
The Union Prototype
public static IEnumerable<T> Union<T>(

this IEnumerable<T> first,
IEnumerable<T> second);
This operator returns an object that, when enumerated, first enumerates the elements of the
input sequence named first, yielding any element that is not equal to a previously yielded element,
then enumerates the second input sequence, again yielding any element that is not equal to a previously
yielded element. An element is determined to be equal to another element using their GetHashCode and
Equals methods.
Exceptions
ArgumentNullException is thrown if any arguments are null.
Examples
To demonstrate the difference between the Union operator and the Concat operator I covered previ-
ously, in the example in Listing 4-35, I will create a first and second sequence from my presidents
array that results in the fifth element being duplicated in both sequences. I will then display the
count of the presidents array and the first and second sequences, as well as the count of a concat-
enated and union sequence.
Listing 4-35. An Example of the Union Operator
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
Rattz_789-3.book Page 113 Tuesday, October 16, 2007 2:21 PM
114
CHAPTER 4
■ DEFERRED OPERATORS
IEnumerable<string> first = presidents.Take(5);
IEnumerable<string> second = presidents.Skip(4);
// Since I only skipped 4 elements, the fifth element

// should be in both sequences.
IEnumerable<string> concat = first.Concat<string>(second);
IEnumerable<string> union = first.Union<string>(second);
Console.WriteLine("The count of the presidents array is: " + presidents.Count());
Console.WriteLine("The count of the first sequence is: " + first.Count());
Console.WriteLine("The count of the second sequence is: " + second.Count());
Console.WriteLine("The count of the concat sequence is: " + concat.Count());
Console.WriteLine("The count of the union sequence is: " + union.Count());
If this works properly, the concat sequence should have one more element than the presidents
array. The union sequence should contain the same number of elements as the presidents array. The
proof, however, is in the pudding:
The count of the presidents array is: 37
The count of the first sequence is: 5
The count of the second sequence is: 33
The count of the concat sequence is: 38
The count of the union sequence is: 37
Success!
Intersect
The Intersect operator returns the set intersection of two source sequences.
Prototypes
The Intersect operator has one prototype I will cover.
The Intersect Prototype
public static IEnumerable<T> Intersect<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
This operator returns an object that, when enumerated, first enumerates the elements of the
input sequence named first, collecting any element that is not equal to a previously collected element. It
then enumerates the second input sequence, marking any element that is in both sequences to be
yielded. It next enumerates through the marked elements yielding them in the order in which they
were collected. An element is determined to be equal to another element using their GetHashCode and

Equals methods.
Exceptions
ArgumentNullException is thrown if any arguments are null.
Rattz_789-3.book Page 114 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
115
Examples
For my example of the Intersect operator in Listing 4-36, I will use the Take and Skip operators to
generate two sequences and get some overlap, just like I did in the Union operator example, where I
intentionally duplicated the fifth element. When I call the Intersect operator on those two generated
sequences, only the duplicated fifth element should be in the returned intersect sequence. I will display
the counts of the presidents array and all the sequences. Lastly, I will enumerate through the intersect
sequence displaying each element, which should only be the fifth element of the presidents array.
Listing 4-36. An Example of the Intersect Operator
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
IEnumerable<string> first = presidents.Take(5);
IEnumerable<string> second = presidents.Skip(4);
// Since I only skipped 4 elements, the fifth element
// should be in both sequences.
IEnumerable<string> intersect = first.Intersect(second);
Console.WriteLine("The count of the presidents array is: " + presidents.Count());
Console.WriteLine("The count of the first sequence is: " + first.Count());
Console.WriteLine("The count of the second sequence is: " + second.Count());
Console.WriteLine("The count of the intersect sequence is: " + intersect.Count());

// Just for kicks, I will display the intersection sequence,
// which should be just the fifth element.
foreach (string name in intersect)
Console.WriteLine(name);
If this works the way it should, I should have an intersect sequence with just one element
containing the duplicated fifth element of the presidents array, "Carter":
The count of the presidents array is: 37
The count of the first sequence is: 5
The count of the second sequence is: 33
The count of the intersect sequence is: 1
Carter
LINQ rocks! How many times have you needed to perform set-type operations on two collections?
Wasn’t it a pain? Thanks to LINQ, those days are gone.
Except
The Except operator returns a sequence that contains all the elements of a first sequence that do not
exist in a second sequence.
Rattz_789-3.book Page 115 Tuesday, October 16, 2007 2:21 PM
116
CHAPTER 4
■ DEFERRED OPERATORS
Prototypes
This operator has one prototype I will cover.
The Except Prototype
public static IEnumerable<T> Except<T>(
this IEnumerable<T> first,
IEnumerable<T> second);
This operator returns an object that, when enumerated, enumerates the elements of the input
sequence named first, collecting any element that is not equal to a previously collected element. It
then enumerates the second input sequence, removing from the collection any element that is in
both sequences. It next enumerates through the collected elements yielding them in the order in

which they were collected. An element is determined to be equal to another element using their
GetHashCode and Equals methods.
Exceptions
ArgumentNullException is thrown if any arguments are null.
Examples
For this example, I will use the presidents array that I use in most of the examples. Imagine a scenario
where you have a primary data source, the presidents array, with entries that you need to perform
some processing on. As you complete the processing of each entry, you want to add it to a collection
of processed entries so that if you need to start processing again, you can use the Except operator to
produce an exception sequence consisting of the primary data source elements, minus the entries
from the processed entry collection. You can then process this exception sequence again without
the concern of reprocessing an entry.
For this example in Listing 4-37, I will pretend that I have already processed the first four entries.
To obtain a sequence containing the first four elements of the presidents array, I will just call the
Take operator on it.
Listing 4-37. An Example of the Except Prototype
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
// First generate a processed sequence.
IEnumerable<string> processed = presidents.Take(4);
IEnumerable<string> exceptions = presidents.Except(processed);
foreach (string name in exceptions)
Console.WriteLine(name);
In this example, my results should contain the names of the presidents array after the fourth
element, "Bush":

Rattz_789-3.book Page 116 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
117
Carter
Cleveland
Clinton
Coolidge
Eisenhower
Fillmore
Ford
Garfield
Grant
Harding
Harrison
Hayes
Hoover
Jackson
Jefferson
Johnson
Kennedy
Lincoln
Madison
McKinley
Monroe
Nixon
Pierce
Polk
Reagan
Roosevelt
Taft

Taylor
Truman
Tyler
Van Buren
Washington
Wilson
That worked just as I would have expected.
Conversion
The conversion operators provide a simple and convenient way of converting sequences to other
collection types.
Cast
The Cast operator is used to cast every element of an input sequence to an output sequence of the
specified type.
Prototypes
The Cast operator has one prototype I will cover.
The Cast Prototype
public static IEnumerable<T> Cast<T>(
this IEnumerable source);
Rattz_789-3.book Page 117 Tuesday, October 16, 2007 2:21 PM
118
CHAPTER 4
■ DEFERRED OPERATORS
The first thing you should notice about the Cast operator is that its first argument, named source, is
of type IEnumerable, not IEnumerable<T>, while most of the deferred Standard Query Operators’ first
arguments are of type IEnumerable<T>. This is because the Cast operator is designed to be called on
classes that implement the IEnumerable interface, as opposed to the IEnumerable<T> interface. In
particular, we are talking about all the legacy C# collections prior to C# 2.0 and generics.
You can call the Cast operator on a legacy C# collection as long as it implements IEnumerable,
and an IEnumerable<T> output sequence will be created. Since most of the Standard Query Operators
only work on IEnumerable<T> type sequences, you must call some method like this one, or perhaps

the OfType operator that I will cover next, to get a legacy collection converted to a sequence the Standard
Query Operators can be called on. This is important when trying to use the Standard Query Operators
on legacy collections.
This operator will return an object that, when enumerated, enumerates the source data collec-
tion, yielding each element cast to type T. If the element cannot be cast to type T, an exception will
be thrown. Because of this, this operator should only be called when it is known that every element
in the sequence can be cast to type T.
■Tip When trying to perform LINQ queries on legacy collections, don’t forget to call Cast or OfType on the legacy
collection to create an IEnumerable<T> sequence that the Standard Query Operators can be called on.
Exceptions
ArgumentNullException is thrown if the source argument is null. InvalidCastException is thrown if
an element in the input source collection cannot be cast to type T.
Examples
For this example, I will use my common Employee class’s GetEmployeesArrayList method to return a
legacy, nongeneric ArrayList.
In Listing 4-38 is some code illustrating how the data type of the elements of an ArrayList get
cast to elements in a sequence, IEnumerable<T>.
Listing 4-38. Code Converting an ArrayList to an IEnumerable<T> That Can Be Used with the Typical
Standard Query Operators
ArrayList employees = Employee.GetEmployeesArrayList();
Console.WriteLine("The data type of employees is " + employees.GetType());
var seq = employees.Cast<Employee>();
Console.WriteLine("The data type of seq is " + seq.GetType());
var emps = seq.OrderBy(e => e.lastName);
foreach (Employee emp in emps)
Console.WriteLine("{0} {1}", emp.firstName, emp.lastName);
First, I call the GetEmployeesArrayList method to return an ArrayList of Employee objects, and
then I display the data type of the employees variable. Next, I convert that ArrayList to an IEnumerable<T>
sequence by calling the Cast operator, and then I display the data type of the returned sequence. Lastly,
I enumerate through that returned sequence to prove that the ordering did indeed work.

Here is the output from the code:
Rattz_789-3.book Page 118 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
119
The data type of employees is System.Collections.ArrayList
The data type of seq is
System.Linq.Enumerable+<CastIterator>d__b0`1[LINQChapter4.Employee]
Kevin Flynn
William Gates
Anders Hejlsberg
David Lightman
Joe Rattz
You can see the data type of the employees variable is an ArrayList. It is a little more difficult
determining what the data type of seq is. We can definitely see it is different, and it looks like a
sequence. We can also see the word CastIterator in its type. Have you noticed that when I discuss
the deferred operators that they don’t actually return the output sequence but really return an object
that, when enumerated, would yield the elements to the output sequence? The seq variable’s data
type displayed in the previous example is just this kind of object. However, this is an implementation
detail and could change.
■Caution The Cast operator will attempt to cast each element in the input sequence to the specified type. If
any of those elements cannot be cast to the specified type, an InvalidCastException exception will be thrown.
If it is at all possible that there may be elements of differing types, use the OfType operator instead.
OfType
The OfType operator is used to build an output sequence containing only the elements that can be
successfully cast to a specified type.
Prototypes
This operator has one prototype I will cover.
The OfType Prototype
public static IEnumerable<T> OfType<T>(
this IEnumerable source);

The first thing you should notice about the OfType operator is that, just like the Cast operator,
its first argument, named source, is of type IEnumerable, not IEnumerable<T>. Most of the deferred
Standard Query Operators’ first arguments are of type IEnumerable<T>. This is because the OfType
operator is designed to be called on classes that implement the IEnumerable interface, as opposed
to the IEnumerable<T> interface. In particular, we are talking about all the legacy C# collections
prior to C# 2.0 and generics.
So, you can call the OfType operator on a legacy C# collection as long as it implements
IEnumerable, and an IEnumerable<T> output sequence will be created. Since most of the Standard
Query Operators only work on IEnumerable<T> type sequences, you must call some method like this
one, or perhaps the Cast operator, to get the legacy collection converted to a sequence the Standard
Query Operators can be called on. This is important when trying to use the Standard Query Operators
on legacy collections.
The OfType operator will return an object that, when enumerated, will enumerate the source
sequence, yielding only those elements whose type matches the type specified, T.
Rattz_789-3.book Page 119 Tuesday, October 16, 2007 2:21 PM
120
CHAPTER 4
■ DEFERRED OPERATORS
The OfType operator differs from the Cast operator in that the Cast operator will attempt to cast
every element of the input sequence to type T and yield it to the output sequence. If the cast fails, an
exception is thrown. The OfType operator will only attempt to yield the input element if it can be cast
to type T. Technically, the element must return true for element e is T for the element to be yielded
to the output sequence.
Exceptions
ArgumentNullException is thrown if the source argument is null.
Examples
For the example in Listing 4-39, I am going to create an ArrayList containing objects of my two
common classes, Employee and EmployeeOptionEntry. Once I have the ArrayList populated with
objects of both classes, I will first call the Cast operator to show how it fails in this circumstance. I will
follow that call with a call to the OfType operator showing its prowess in the same situation.

Listing 4-39. Sample Code Calling the Cast and OfType Operator
ArrayList al = new ArrayList();
al.Add(new Employee { id = 1, firstName = "Joe", lastName = "Rattz" });
al.Add(new Employee { id = 2, firstName = "William", lastName = "Gates" });
al.Add(new EmployeeOptionEntry { id = 1, optionsCount = 0 });
al.Add(new EmployeeOptionEntry { id = 2, optionsCount = 99999999999 });
al.Add(new Employee { id = 3, firstName = "Anders", lastName = "Hejlsberg" });
al.Add(new EmployeeOptionEntry { id = 3, optionsCount = 848475745 });
var items = al.Cast<Employee>();
Console.WriteLine("Attempting to use the Cast operator ");
try
{
foreach (Employee item in items)
Console.WriteLine("{0} {1} {2}", item.id, item.firstName, item.lastName);
}
catch (Exception ex)
{
Console.WriteLine("{0}{1}", ex.Message, System.Environment.NewLine);
}
Console.WriteLine("Attempting to use the OfType operator ");
var items2 = al.OfType<Employee>();
foreach (Employee item in items2)
Console.WriteLine("{0} {1} {2}", item.id, item.firstName, item.lastName);
Once I have the ArrayList created and populated, I call the Cast operator. The next step is to try
to enumerate it. This is a necessary step because the Cast operator is deferred. If I never enumerate
the results of that query, it will never be performed, and I would not detect a problem. Notice that I
wrapped the foreach loop that enumerates the query results with a try/catch block. This is necessary
in this case, because I know an exception will be thrown since there are objects of two completely
different types. Next, I call the OfType operator and enumerate and display its results. Notice my
pluck as I brazenly choose not to wrap my foreach loop in a try/catch block. Of course, in your real

production code, you wouldn’t want to ignore the protection a try/catch block offers.
Here are the results of this query:
Rattz_789-3.book Page 120 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
121
Attempting to use the Cast operator
1 Joe Rattz
2 William Gates
Unable to cast object of type 'LINQChapter4.EmployeeOptionEntry' to type
'LINQChapter4.Employee'.
Attempting to use the OfType operator
1 Joe Rattz
2 William Gates
3 Anders Hejlsberg
Notice that I was not able to completely enumerate the query results of the Cast operator without
an exception being thrown. But, I was able to enumerate the query results of the OfType operator, and
only elements of type Employee were included in the output sequence.
The moral of this story is that if it is feasible that the input sequence contains elements of more
than one data type, prefer the OfType operator to the Cast operator.
■Tip If you are trying to convert a nongeneric collection, such as the legacy collection classes, to an IEnumerable<T>
type that can be used with the Standard Query Operators operating on that type, use the OfType operator instead
of the Cast operator if it is possible that the input collection could contain objects of differing types.
AsEnumerable
The AsEnumerable operator simply causes its input sequence of type IEnumerable<T> to be returned
as type IEnumerable<T>.
Prototypes
The AsEnumerable operator has one prototype I will cover.
The AsEnumerable Prototype
public static IEnumerable<T> AsEnumerable<T>(
this IEnumerable<T> source);

The preceding prototype declares that the AsEnumerable operator operates on an IEnumerable<T>
named source and returns that same sequence typed as IEnumerable<T>. It serves no other purpose
than changing the output sequence type at compile time.
This may seem odd since it must be called on an IEnumerable<T>. You may ask, “Why would you
possibly need to convert a sequence of type IEnumerable<T> to a sequence of type IEnumerable<T>?”
That would be a good question.
The Standard Query Operators are declared to operate on normal LINQ to Objects sequences,
those collections implementing the IEnumerable<T> interface. However, other domain’s collections,
such as those for accessing a database, could choose to implement their own sequence type and
operators. Ordinarily, when calling a query operator on a collection of one of those types, a collec-
tion-specific operator would be called. The AsEnumerable operator allows the input sequence to be
cast as a normal IEnumerable<T> sequence, allowing a Standard Query Operator method to be called.
For example, when I cover LINQ to SQL in a later part of this book, you will see that LINQ to SQL
actually uses its own type of sequence, IQueryable<T>, and implements its own operators. The LINQ
to SQL operators will be called on sequences of type IQueryable<T>. When you call the Where method
Rattz_789-3.book Page 121 Tuesday, October 16, 2007 2:21 PM
122
CHAPTER 4
■ DEFERRED OPERATORS
on a sequence of type IQueryable<T>, it is the LINQ to SQL Where method that will get called, not the
LINQ to Objects Standard Query Operator Where method. In fact, without the AsEnumerable method,
you cannot call a Standard Query Operator on a sequence of type IQueryable<T>. If you try to call one
of the Standard Query Operators, you will get an exception unless a LINQ to SQL operator exists with
the same name, and the LINQ to SQL operator will be called. With the AsEnumerable operator, you
can call it to cast the IQueryable<T> sequence to an IEnumerable<T> sequence, thereby allowing Standard
Query Operators to be called. This becomes very handy when you need to control in which API an
operator is called.
Exceptions
There are no exceptions.
Examples

To better understand this operator, I need a situation where a domain-specific operator is imple-
mented. For that, I need a LINQ to SQL example. I will start with the first LINQ to SQL example in this
book from Chapter 1. For your perusal, here is that example.
Reprinted Here for Convenience Is Listing 1-3
using System;
using System.Linq;
using System.Data.Linq;
using nwind;
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind");
var custs =
from c in db.Customers
where c.City == "Rio de Janeiro"
select c;
foreach (var cust in custs)
Console.WriteLine("{0}", cust.CompanyName);
Here are the results of that example:
Hanari Carnes
Que Delícia
Ricardo Adocicados
For that example to work, you must add the System.Data.Linq.dll assembly to your project,
add a using directive for the nwind namespace, and add the generated entity classes that I will cover
in the LINQ to SQL chapters to your project. Additionally, you may need to tweak the connection
string.
Let’s assume that I need to reverse the order of the records coming from the database for some
reason. I am not concerned because I know there is a Reverse operator that I covered earlier in this
chapter. Listing 4-40 shows the previous example modified to call the Reverse operator.
Rattz_789-3.book Page 122 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
123
Listing 4-40. Calling the Reverse Operator

Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind");
var custs =
(from c in db.Customers
where c.City == "Rio de Janeiro"
select c)
.Reverse();
foreach (var cust in custs)
Console.WriteLine("{0}", cust.CompanyName);
It seems simple enough. As you can see, my only change is to add the call to the Reverse method.
The code compiles just fine. Here are the results of the example:
Unhandled Exception: System.NotSupportedException: The query operator 'Reverse' is
not supported.

Boy, that seemed like it should have been so simple, what happened? What happened is that
there is no Reverse method for the IQueryable<T> interface, so the exception was thrown. I need to
use the AsEnumerable method to convert the sequence of type IQueryable<T> to a sequence of type
IEnumerable<T> so that when I call the Reverse method, the IEnumerable<T> Reverse method gets
called. The code modified to do this is in Listing 4-41.
Listing 4-41. Calling the AsEnumerable Operator Before Calling the Reverse Operator
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind");
var custs =
(from c in db.Customers
where c.City == "Rio de Janeiro"
select c)
.AsEnumerable()
.Reverse();
foreach (var cust in custs)
Console.WriteLine("{0}", cust.CompanyName);
Now, I am calling the AsEnumerable method first, followed by the Reverse operator, so the LINQ
to Objects Reverse operator will be called. Here are the results:

Ricardo Adocicados
Que Delícia
Hanari Carnes
Those results are in the reverse order of the initial example, so it worked.
Element
The element operators allow you to retrieve single elements from an input sequence.
Rattz_789-3.book Page 123 Tuesday, October 16, 2007 2:21 PM
124
CHAPTER 4
■ DEFERRED OPERATORS
DefaultIfEmpty
The DefaultIfEmpty operator returns a sequence containing a default element if the input source
sequence is empty.
Prototypes
There are two prototypes for the DefaultIfEmpty operator I will cover.
The First DefaultIfEmpty Prototype
public static IEnumerable<T> DefaultIfEmpty<T>(
this IEnumerable<T> source);
This prototype of the DefaultIfEmpty operator returns an object that, when enumerated, enumer-
ates the input source sequence, yielding each element unless the source sequence is empty, in which
case it returns a sequence yielding a single element of default(T). For reference and nullable types,
the default value is null.
Unlike all the other element type operators, notice that DefaultIfEmpty returns a sequence of
type IEnumerable<T> instead of a type T. There are additional element type operators, but they are not
included in this chapter, because they are not deferred operators.
The second prototype allows the default value to be specified.
The Second DefaultIfEmpty Prototype
public static IEnumerable<T> DefaultIfEmpty<T>(
this IEnumerable<T> source,
T defaultValue);

This operator is useful for all the other operators that throw exceptions if the input source
sequence is empty. Additionally, this operator is useful in conjunction with the GroupJoin operator
for producing left outer joins.
Exceptions
ArgumentNullException is thrown if the source argument is null.
Examples
Listing 4-42 shows the example of the first DefaultIfEmpty prototype with an empty sequence. In
this example, I will not use the DefaultIfEmpty operator to see what happens. I will search my presi-
dents array for "Jones", return the first element, and if it’s not null, output a message.
Listing 4-42. The First Example for the First DefaultIfEmpty Prototype, Without Using DefaultIfEmpty
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
Rattz_789-3.book Page 124 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
125
string jones = presidents.Where(n => n.Equals("Jones")).First();
if (jones != null)
Console.WriteLine("Jones was found");
else
Console.WriteLine("Jones was not found");
Here are the results:
Unhandled Exception: System.InvalidOperationException: Sequence contains no elements

In the preceding code, the query didn’t find any elements equal to "Jones", so an empty sequence
was passed to the First operator. The First operator doesn’t like empty sequences, so an exception

is thrown.
Now, in Listing 4-43, I will call the same code, except I will insert a call to the DefaultIfEmpty
operator between the Where operator and the First operator. This way, instead of an empty sequence,
a sequence containing a null element will be passed to First.
Listing 4-43. The Second Example for the First DefaultIfEmpty Prototype, Using DefaultIfEmpty
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
string jones = presidents.Where(n => n.Equals("Jones")).DefaultIfEmpty().First();
if (jones != null)
Console.WriteLine("Jones was found.");
else
Console.WriteLine("Jones was not found.");
The results now are
Jones was not found.
For an example of the second prototype, I am allowed to specify the default value for an empty
sequence, as shown in Listing 4-44.
Listing 4-44. An Example for the Second DefaultIfEmpty Prototype
string[] presidents = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
Rattz_789-3.book Page 125 Tuesday, October 16, 2007 2:21 PM

126
CHAPTER 4
■ DEFERRED OPERATORS
string name =
presidents.Where(n => n.Equals("Jones")).DefaultIfEmpty("Missing").First();
Console.WriteLine(name);
The results are
Missing
Next, for one last set of examples, I will perform a left outer join using both the GroupJoin and
DefaultIfEmpty operators. I will use my two common classes, Employee and EmployeeOptionEntry. In
Listing 4-45 is an example without using the DefaultIfEmpty operator.
Listing 4-45. An Example Without the DefaultIfEmpty Operator
ArrayList employeesAL = Employee.GetEmployeesArrayList();
// Add a new employee so one employee will have no EmployeeOptionEntry records.
employeesAL.Add(new Employee {
id = 102,
firstName = "Michael",
lastName = "Bolton" });
Employee[] employees = employeesAL.Cast<Employee>().ToArray();
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();
var employeeOptions = employees
.GroupJoin(
empOptions,
e => e.id,
o => o.id,
(e, os) => os
.Select(o => new
{
id = e.id,
name = string.Format("{0} {1}", e.firstName, e.lastName),

options = o != null ? o.optionsCount : 0
}))
.SelectMany(r => r);
foreach (var item in employeeOptions)
Console.WriteLine(item);
There are three things I want to point out about this example. First, it is very similar to the example
I presented for the GroupJoin operator example when I discussed it. Second, since my common
EmployeeOptionEntry class already has a matching object for every employee in the common Employee
class, I am getting the ArrayList of employees and adding a new employee, Michael Bolton, to it so
that I will have one employee with no matching EmployeeOptionEntry objects. Third, I am not making
a call to the DefaultIfEmpty operator in that example.
The results of this query are
{ id = 1, name = Joe Rattz, options = 2 }
{ id = 2, name = William Gates, options = 10000 }
{ id = 2, name = William Gates, options = 10000 }
{ id = 2, name = William Gates, options = 10000 }
Rattz_789-3.book Page 126 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
127
{ id = 3, name = Anders Hejlsberg, options = 5000 }
{ id = 3, name = Anders Hejlsberg, options = 7500 }
{ id = 3, name = Anders Hejlsberg, options = 7500 }
{ id = 4, name = David Lightman, options = 1500 }
{ id = 101, name = Kevin Flynn, options = 2 }
Please notice that, since there were no matching objects in the EmployeeOptionEntry array for
employee Michael Bolton, I got no record for that employee in the output sequence. By using the
DefaultIfEmpty operator, I can provide a matching default record, as shown in Listing 4-46.
Listing 4-46. An Example with the DefaultIfEmpty Operator
ArrayList employeesAL = Employee.GetEmployeesArrayList();
// Add a new employee so one employee will have no EmployeeOptionEntry records.

employeesAL.Add(new Employee {
id = 102,
firstName = "Michael",
lastName = "Bolton" });
Employee[] employees = employeesAL.Cast<Employee>().ToArray();
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();
var employeeOptions = employees
.GroupJoin(
empOptions,
e => e.id,
o => o.id,
(e, os) => os
.DefaultIfEmpty()
.Select(o => new
{
id = e.id,
name = string.Format("{0} {1}", e.firstName, e.lastName),
options = o != null ? o.optionsCount : 0
}))
.SelectMany(r => r);
foreach (var item in employeeOptions)
Console.WriteLine(item);
In the preceding example, I am still adding an employee object for Michael Bolton with no
matching EmployeeOptionEntry objects. I am now calling the DefaultIfEmpty operator. Here are the
results of my resulting left outer join:
{ id = 1, name = Joe Rattz, options = 2 }
{ id = 2, name = William Gates, options = 10000 }
{ id = 2, name = William Gates, options = 10000 }
{ id = 2, name = William Gates, options = 10000 }
{ id = 3, name = Anders Hejlsberg, options = 5000 }

{ id = 3, name = Anders Hejlsberg, options = 7500 }
{ id = 3, name = Anders Hejlsberg, options = 7500 }
{ id = 4, name = David Lightman, options = 1500 }
{ id = 101, name = Kevin Flynn, options = 2 }
{ id = 102, name = Michael Bolton, options = 0 }
Rattz_789-3.book Page 127 Tuesday, October 16, 2007 2:21 PM
128
CHAPTER 4
■ DEFERRED OPERATORS
As you can see, I now have a record for Michael Bolton even though there are no matching
EmployeeOptionEntry objects. From the results, you can see Michael Bolton has received no employee
options. Indeed, it is no wonder he was always so irritated with that printer.
Generation
The generation operators assist with generating sequences.
Range
The Range operator generates a sequence of integers.
Prototypes
There is one prototype for the Range operator I will cover.
The Range Prototype
public static IEnumerable<int> Range(
int start,
int count);
A sequence of integers will be generated starting with the value passed as start and continuing
for the number of count.
Notice that this is not an extension method and one of the few Standard Query Operators that
does not extend IEnumerable<T>.
■Note Range is not an extension method. It is a static method called on System.Linq.Enumerable.
Exceptions
ArgumentOutOfRangeException is thrown if the count is less than zero, or if start plus count minus
one is greater than int.MaxValue.

Examples
Listing 4-47. An Example Calling the Range Operator
IEnumerable<int> ints = Enumerable.Range(1, 10);
foreach(int i in ints)
Console.WriteLine(i);
Again, I want to stress that I am not calling the Range operator on a sequence. It is a static method
of the System.Linq.Enumerable class. There are no surprises here, as the results prove:
1
2
3
4
5
6
7
Rattz_789-3.book Page 128 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
129
8
9
10
Repeat
The Repeat operator generates a sequence by repeating a specified element a specified number of times.
Prototypes
The Repeat operator has one prototype I will cover.
The Repeat Prototype
public static IEnumerable<T> Repeat<T>(
T element,
int count);
This prototype returns an object that, when enumerated, will yield count number of T elements.
Notice that this is not an extension method and one of the few Standard Query Operators that

does not extend IEnumerable<T>.
■Note Repeat is not an extension method. It is a static method called on System.Linq.Enumerable.
Exceptions
ArgumentOutOfRangeException is thrown if the count is less than zero.
Examples
In Listing 4-48 I will generate a sequence containing ten elements where each element is the number 2.
Listing 4-48. Returning a Sequence of Ten Integers All With the Value Two
IEnumerable<int> ints = Enumerable.Repeat(2, 10);
foreach(int i in ints)
Console.WriteLine(i);
Here are the results of this example:
2
2
2
2
2
2
2
2
2
2
Rattz_789-3.book Page 129 Tuesday, October 16, 2007 2:21 PM
130
CHAPTER 4
■ DEFERRED OPERATORS
Empty
The Empty operator generates an empty sequence of a specified type.
Prototypes
The Empty operator has one prototype I will cover.
The Empty Prototype

public static IEnumerable<T> Empty<T>();
This prototype returns an object that, when enumerated, will return a sequence containing zero
elements of type T.
Notice that this is not an extension method and one of the few Standard Query Operators that
does not extend IEnumerable<T>.
■Note Empty is not an extension method. It is a static method called on System.Linq.Enumerable.
Exceptions
There are no exceptions.
Examples
In Listing 4-49 I generate an empty sequence of type string using the Empty operator and display the
Count of the generated sequence, which should be zero since the sequence is empty.
Listing 4-49. An Example to Return an Empty Sequence of Strings
IEnumerable<string> strings = Enumerable.Empty<string>();
foreach(string s in strings)
Console.WriteLine(s);
Console.WriteLine(strings.Count());
Here is the output of the preceding code:
0
Since the sequence is empty, there are no elements to display in the foreach loop, so I added the
display of the count of the number of elements in the sequence.
Rattz_789-3.book Page 130 Tuesday, October 16, 2007 2:21 PM
CHAPTER 4 ■ DEFERRED OPERATORS
131
Summary
I know this has been a whirlwind tour of the deferred Standard Query Operators. I have attempted to
provide examples for virtually every prototype of each deferred operator, instead of just the simplest
prototype. I always dislike it when books show the simplest form of calling a method but leave it to
you to figure out the more complex versions. Hopefully, I will have made calling the more complex
prototypes simple for you.
Additionally, I hope that by breaking up the Standard Query Operators into those that are deferred

and those that are not, I have properly emphasized the significance this can have on your queries.
While this chapter covered the bulk of the Standard Query Operators, in the next chapter I will
conclude my coverage of LINQ to Objects with an examination of the nondeferred Standard Query
Operators.
Rattz_789-3.book Page 131 Tuesday, October 16, 2007 2:21 PM
Rattz_789-3.book Page 132 Tuesday, October 16, 2007 2:21 PM

×