The Distinct Prototype public static IEnumerable Distinct this IEnumerable source; This operator returns an object that, when enumerated, enumerates the elements of the input sequence n
Trang 1In 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
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
Trang 2A Class Implementing the IEqualityComparer Interface for My Second GroupBy Example
public class MyFounderNumberComparer : IEqualityComparer<int>
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);
}
Trang 3In 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
Trang 4For 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
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
Trang 5The 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?
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());
Trang 6If 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?
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"};
Trang 7IEnumerable<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
Trang 8For 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
Trang 9This 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
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":
Trang 10The Cast operator has one prototype I will cover.
The Cast Prototype
public static IEnumerable<T> Cast<T>(
this IEnumerable source);
Trang 11The 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 tion, yielding each element cast to type T If the element cannot be cast to type T, an exception will
collec-be thrown Because of this, this operator should only collec-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
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:
Trang 12The data type of employees is System.Collections.ArrayList
The data type of seq is
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
Trang 13The 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
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);
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:
Trang 14Attempting to use the Cast operator
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
Trang 15on 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
imple-Reprinted Here for Convenience Is Listing 1-3
Trang 16Listing 4-40 Calling the Reverse Operator
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind");
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");
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:
Trang 17The 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, 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
enumer-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
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"};
Trang 18string 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"};
Trang 19Listing 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
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 }
Trang 20{ 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
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 }
Trang 21As 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.
There is one prototype for the Range operator I will cover
The Range Prototype
public static IEnumerable<int> Range(
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:
Trang 22The 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);
Trang 23The 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
Listing 4-49 An Example to Return an Empty Sequence of Strings
IEnumerable<string> strings = Enumerable.Empty<string>();
Trang 24I 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
Trang 26■ ■ ■
C H A P T E R 5
Nondeferred Operators
because they return either IEnumerable<T> or OrderedSequence<T> But the deferred operators are
only half the Standard Query Operator story For the full story, I must also cover the nondeferred
query operators A nondeferred operator is easy to spot because it has a return data type other than
IEnumerable<T> or OrderedSequence<T> These nondeferred operators are categorized in this chapter
by their purpose
In order to code and execute the examples in this chapter, you will need to make sure you have
using directives for all the necessary namespaces You must also have some common code that the
examples share
Referenced Namespaces
The examples in this chapter will use the System.Linq, System.Collections, and System.Collections
Generic namespaces Therefore, you should add the following using directives to your code if they
are not present:
using System.Linq;
using System.Collections;
using System.Collections.Generic;
In addition to these namespaces, if you download the companion code, you will see that I have
also added a using directive for the System.Diagnostics namespace This will not be necessary if you
are typing in the examples from this chapter It is necessary in the companion code due to some
housekeeping code
Common Classes
Several of the examples in this chapter require classes to fully demonstrate an operator’s behavior
This section describes four classes that will be shared by more than one example, beginning with the
Employee class
The Employee class is meant to represent an employee For convenience, it contains static
methods to return an ArrayList or array of employees