Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 52 trang
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
Nội dung
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 [...]... 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)... Here is my MyStringifiedNumberComparer class that does just that: The Shared MyStringifiedNumberComparer Class public class MyStringifiedNumberComparer : IEqualityComparer { public bool Equals(string x, string y) { return(Int32.Parse(x) == Int32.Parse(y)); } public int GetHashCode(string obj) { return Int32.Parse(obj).ToString().GetHashCode(); } } 135 Rattz_789 -3. book Page 136 Tuesday, October... Sequence of Strings IEnumerable strings = Enumerable.Empty(); 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 131 Tuesday,... 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 ints = Enumerable.Repeat(2, 10); foreach(int i in ints) Console.WriteLine(i); Here are... IQueryable, 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 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... 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... 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 131 Rattz_789 -3. book Page 132 Tuesday,... 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 Range( int start, int count); A sequence of integers will be generated starting with the value passed as start and continuing for the number... In this prototype, a Dictionary of type is created and returned by enumerating the input sequence named source The keySelector method delegate is called to extract the key value from each input element, and that key is the element’s key into the Dictionary This version of the operator results in elements in the Dictionary being the same type as the elements in the input sequence Since this prototype... 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 . 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. 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[]. 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