1. Trang chủ
  2. » Công Nghệ Thông Tin

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

52 428 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 52
Dung lượng 873,86 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

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

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 2

A 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 3

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

Trang 4

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

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 5

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?

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 6

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?

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 7

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

Trang 8

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

Trang 9

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

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 10

The Cast operator has one prototype I will cover.

The Cast Prototype

public static IEnumerable<T> Cast<T>(

this IEnumerable source);

Trang 11

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 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 12

The 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 13

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

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 14

Attempting 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 15

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

imple-Reprinted Here for Convenience Is Listing 1-3

Trang 16

Listing 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 17

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, 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 18

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"};

Trang 19

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

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 21

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.

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 22

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);

Trang 23

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

Listing 4-49 An Example to Return an Empty Sequence of Strings

IEnumerable<string> strings = Enumerable.Empty<string>();

Trang 24

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

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

Ngày đăng: 06/08/2014, 08:22

TỪ KHÓA LIÊN QUAN