Essential C# 3.0 FOR NET FRAMEWORK 3.5 PHẦN 7 doc

87 1.1K 0
Essential C# 3.0 FOR NET FRAMEWORK 3.5 PHẦN 7 doc

Đ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

Summary Using recursion, the PrintNode() function demonstrates that an expression tree is a tree of zero or more expression trees The contained expression trees are stored in an Expression’s Body property In addition, the expression tree includes an ExpressionType property called NodeType where ExpressionType is an enum for each different type of expression There are numerous types of expressions: BinaryExpression, ConditionalExpression, LambdaExpression (the root of an expression tree), MethodCallExpression, ParameterExpression, and ConstantExpression are examples Each type derives from System.Linq.Expressions.Expression Generally, you can use statement lambdas interchangeably with expression lambdas However, you cannot convert statement lambdas into expression trees You can express expression trees only by using expression lambda syntax SUMMARY This chapter began with a discussion of delegates and their use as references to methods or callbacks It introduced a powerful concept for passing a set of instructions to call in a different location, rather than immediately, when the instructions are coded Following on the heels of a brief look at the C# 2.0 concept of anonymous methods, the chapter introduced the C# 3.0 concept of lambda expressions, a syntax that supersedes (although doesn’t eliminate) the C# 2.0 anonymous method syntax Regardless of the syntax, these constructs allow programmers to assign a set of instructions to a variable directly, without defining an explicit method that contains the instructions This provides significant flexibility for programming instructions dynamically within the method—a powerful concept that greatly simplifies the programming of collections through an API known as LINQ, for language integrated query Finally, the chapter ended with the concept of expression trees, and how they compile into data that represents a lambda expression, rather than the delegate implementation itself This is a key feature that enables such libraries as LINQ to SQL and LINQ to XML, libraries that interpret the expression tree and use it within contexts other than CIL 477 478 Chapter 12: Delegates and Lambda Expressions The term lambda expression encompasses both statement lambda and expression lambda In other words, statement lambdas and expression lambdas are both types of lambda expressions One thing the chapter mentioned but did not elaborate on was multicast delegates The next chapter investigates multicast delegates in detail and explains how they enable the publish-subscribe pattern with events 13 Events you saw how to store a single method inside an instance of a delegate type and invoke that method via the delegate Delegates comprise the building blocks of a larger pattern called publish-subscribe The use of delegates and their support for publishsubscribe patterns is the focus of this chapter Virtually everything described within this chapter is possible to using delegates alone However, the event constructs that this chapter focuses on provide important encapsulation, making the publish-and-subscribe pattern easier to implement and less error-prone In the preceding chapter, all delegates were for a single callback (a multiplicity of one) However, a single delegate variable can reference a series of delegates in which each successive one points to a succeeding delegate I N THE PRECEDING CHAPTER , Customizing the Event Implementation Why Events? Event Declaration Events Generics and Delegates Coding Conventions 479 480 Chapter 13: Events in the form of a chain, sometimes known as a multicast delegate With a multicast delegate, you can call a method chain via a single method object, create variables that refer to a method’s chain, and use those data types as parameters to pass methods The C# implementation of multicast delegates is a common pattern that would otherwise require significant manual code Known as the observer or publish-subscribe pattern, it represents scenarios where notifications of single events, such as a change in object state, are broadcast to multiple subscribers Coding the Observer Pattern with Multicast Delegates Consider a temperature control example, where a heater and a cooler are hooked up to the same thermostat In order for a unit to turn on and off appropriately, you notify the unit of changes in temperature One thermostat publishes temperature changes to multiple subscribers—the heating and cooling units The next section investigates the code.1 Defining Subscriber Methods Begin by defining the Heater and Cooler objects (see Listing 13.1) Listing 13.1: Heater and Cooler Event Subscriber Implementations class Cooler { public Cooler(float temperature) { Temperature = temperature; } public float Temperature { get{return _Temperature;} set{_Temperature = value;} } private float _Temperature; public void OnTemperatureChanged(float newTemperature) In this example, I use the term thermostat because people more commonly think of it in the context of heating and cooling systems Technically, however, thermometer would be more appropriate Coding the Observer Pattern with Multicast Delegates { if (newTemperature > Temperature) { System.Console.WriteLine("Cooler: On"); } else { System.Console.WriteLine("Cooler: Off"); } } } class Heater { public Heater(float temperature) { Temperature = temperature; } public float Temperature { get { return _Temperature; } set { _Temperature = value; } } private float _Temperature; public void OnTemperatureChanged(float newTemperature) { if (newTemperature < Temperature) { System.Console.WriteLine("Heater: On"); } else { System.Console.WriteLine("Heater: Off"); } } } The two classes are essentially identical, with the exception of the temperature comparison (In fact, you could eliminate one of the classes if you used a delegate as a method pointer for comparison within the 481 Chapter 13: Events 482 OnTemperatureChanged method.) Each class stores the temperature for when to turn on the unit In addition, both classes provide an OnTemperatureChanged() method Calling the OnTemperatureChanged() method is the means to indicate to the Heater and Cooler classes that the temperature has changed The method implementation uses newTemperature to compare against the stored trigger temperature to determine whether to turn on the device The OnTemperatureChanged() methods are the subscriber methods It is important that they have the parameters and a return type that matches the delegate from the Thermostat class, which I will discuss next Defining the Publisher The Thermostat class is responsible for reporting temperature changes to the heater and cooler object instances The Thermostat class code appears in Listing 13.2 Listing 13.2: Defining the Event Publisher, Thermostat public class Thermostat { // Define the delegate data type public delegate void TemperatureChangeHandler( float newTemperature); // Define the event publisher public TemperatureChangeHandler OnTemperatureChange { get{ return _OnTemperatureChange;} set{ _OnTemperatureChange = value;} } private TemperatureChangeHandler _OnTemperatureChange; public float CurrentTemperature { get{return _CurrentTemperature;} set { if (value != CurrentTemperature) { _CurrentTemperature = value; } } } private float _CurrentTemperature; } Coding the Observer Pattern with Multicast Delegates The first member of the Thermostat class is the TemperatureChangeHandler delegate Although not a requirement, Thermostat.TemperatureChangeHandler is a nested delegate because its definition is specific to the Thermostat class The delegate defines the signature of the subscriber methods Notice, therefore, that in both the Heater and the Cooler classes, the OnTemperatureChanged() methods match the signature of TemperatureChangeHandler In addition to defining the delegate type, Thermostat includes a property called OnTemperatureChange that is of the OnTemperatureChangeHandler delegate type OnTemperatureChange stores a list of subscribers Notice that only one delegate field is required to store all the subscribers In other words, both the Cooler and the Heater classes will receive notifications of a change in the temperature from this single publisher The last member of Thermostat is the CurrentTemperature property This sets and retrieves the value of the current temperature reported by the Thermostat class Hooking Up the Publisher and Subscribers Finally, put all these pieces together in a Main() method Listing 13.3 shows a sample of what Main() could look like Listing 13.3: Connecting the Publisher and Subscribers class Program { public static void Main() { Thermostat thermostat = new Thermostat(); Heater heater = new Heater(60); Cooler cooler = new Cooler(80); string temperature; // Using C# 2.0 or later syntax thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; Console.Write("Enter temperature: "); temperature = Console.ReadLine(); thermostat.CurrentTemperature = int.Parse(temperature); } } 483 Chapter 13: Events 484 The code in this listing has registered two subscribers (heater.OnTemperatureChanged and cooler.OnTemperatureChanged) to the OnTemperatureChange delegate by directly assigning them using the += operator As noted in the comment, you need to use the new operator with the TemperatureChangeHandler constructor if you are only using C# 1.0 By taking the temperature value the user has entered, you can set the CurrentTemperature of thermostat However, you have not yet written any code to publish the change temperature event to subscribers Invoking a Delegate Every time the CurrentTemperature property on the Thermostat class changes, you want to invoke the delegate to notify the subscribers (heater and cooler) of the change in temperature To this, modify the CurrentTemperature property to save the new value and publish a notification to each subscriber The code modification appears in Listing 13.4 Listing 13.4: Invoking a Delegate without Checking for null public class Thermostat { public float CurrentTemperature { get{return _CurrentTemperature;} set { if (value != CurrentTemperature) { _CurrentTemperature = value; // INCOMPLETE: Check for null needed // Call subscribers OnTemperatureChange(value); } } } private float _CurrentTemperature; } Now the assignment of CurrentTemperature includes some special logic to notify subscribers of changes in CurrentTemperature The call to notify all subscribers is simply the single C# statement, OnTemperatureChange(value) This single statement publishes the temperature change to the cooler and Coding the Observer Pattern with Multicast Delegates heater objects Here, you see in practice that the ability to notify multiple subscribers using a single call is why delegates are more specifically known as multicast delegates Check for null One important part of publishing an event code is missing from Listing 13.4 If no subscriber registered to receive the notification, then OnTemperatureChange would be null and executing the OnTemperatureChange(value) statement would throw a NullReferenceException To avoid this, it is necessary to check for null before firing the event Listing 13.5 demonstrates how to this Listing 13.5: Invoking a Delegate public class Thermostat { public float CurrentTemperature { get{return _CurrentTemperature;} set { if (value != CurrentTemperature) { _CurrentTemperature = value; // If there are any subscribers // then notify them of changes in // temperature TemperatureChangeHandler localOnChange = OnTemperatureChange; if(localOnChange != null) { // Call subscribers localOnChange(value); } } } } private float _CurrentTemperature; } Instead of checking for null directly, first assign OnTemperatureChange to a second delegate variable, handlerCopy This simple modification ensures that if all OnTemperatureChange subscribers are removed (by a different thread) between checking for null and sending the notification, you will not fire a NullReferenceException 485 486 Chapter 13: Events One more time: Remember to check the value of a delegate for null before invoking it ADVANCED TOPIC –= Operator for a Delegate Returns a New Instance Given that a delegate is a reference type, it is perhaps somewhat surprising that assigning a local variable and then using that local variable is sufficient for making the null check thread-safe Since localOnChange points at the same location that OnTemperatureChange points, one would think that any changes in OnTemperatureChange would be reflected in localOnChange as well This is not the case, because effectively, any calls to OnTemperatureChange –= will not simply remove a delegate from OnTemperatureChange so that it contains one less delegate than before Rather, it will assign an entirely new multicast delegate without having any effect on the original multicast delegate to which localOnChange also points Delegate Operators To combine the two subscribers in the Thermostat example, you used the += operator This takes the first delegate and adds the second delegate to the chain so that one delegate points to the next Now, after the first delegate’s method is invoked, it calls the second delegate To remove delegates from a delegate chain, use the –= operator, as shown in Listing 13.6 Listing 13.6: Using the += and –= Delegate Operators // Thermostat thermostat = new Thermostat(); Heater heater = new Heater(60); Cooler cooler = new Cooler(80); Thermostat.TemperatureChangeHandler delegate1; Thermostat.TemperatureChangeHandler delegate2; Thermostat.TemperatureChangeHandler delegate3; // use Constructor syntax for C# 1.0 delegate1 = heater.OnTemperatureChanged; delegate2 = cooler.OnTemperatureChanged; Standard Query Operators new object[] { new object(), 1, 3, 5, 7, 9, "\"thing\"", Guid.NewGuid() }; Print("Stuff: {0}", stuff); IEnumerable even = new int[] { 0, 2, 4, 6, }; Print("Even integers: {0}", even); IEnumerable odd = stuff.OfType(); Print("Odd integers: {0}", odd); IEnumerable numbers = even Union(odd); Print("Union of odd and even: {0}", numbers); Print("Union with even: {0}", numbers.Union(even)); Print("Concat with odd: {0}", numbers.Concat(odd)); Print("Intersection with even: {0}", numbers.Intersect(even)); Print("Distinct: {0}", numbers.Concat(odd).Distinct() ); if (!numbers.SequenceEqual( numbers.Concat(odd).Distinct())) { throw new Exception("Unexpectedly unequal"); } else { Console.WriteLine( @"Collection ""SequenceEquals""" + " collection.Concat(odd).Distinct())"); Print("Reverse: {0}", numbers.Reverse()); Print("Reverse: {0}", numbers.Reverse()); Print("Reverse: {0}", numbers.Average()); Print("Sum: {0}", numbers.Sum()); Print("Max: {0}", numbers.Max()); Print("Min: {0}", numbers.Min()); } } private static void Print( string format, IEnumerable items) { StringBuilder text = new StringBuilder(); foreach (T item in items.Take(items.Count()-1)) { text.Append(item + ", "); } text.Append(items.Last()); Console.WriteLine(format, text); } private static void Print(string format, T item) 549 Chapter 14: Collection Interfaces with Standard Query Operators 550 { Console.WriteLine(format, item); } } OUTPUT 14.12: Stuff: System.Object, 1, 3, 5, 7, 9, "thing", 24c24a41-ee05-41b9-958e50dd12e3981e Even integers: 0, 2, 4, 6, Odd integers: 1, 3, 5, 7, Union of odd and even: 0, 2, 4, 6, 8, 1, 3, 5, 7, Union with even: 0, 2, 4, 6, 8, 1, 3, 5, 7, Concat with odd: 0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 1, 3, 5, 7, Intersection with even: 0, 2, 4, 6, Distinct: 0, 2, 4, 6, 8, 1, 3, 5, 7, Collection "SequenceEquals"collection.Concat(odd).Distinct()) Reverse: 9, 7, 5, 3, 1, 8, 6, 4, 2, Average: 4.5 Sum: 45 Max: Min: None of the API calls in Listing 14 20 requires a lambda expression Table 14.1 and Table 14.2 describe each method TABLE 14.1: Simpler Standard Query Operators Comment Type Description OfType() Forms a query over a collection that returns only the items of a particular type, where the type is identified in the type parameter of the OfType() method call Union() Combines two collections to form a superset of all the items in both collections The final collection does not include duplicate items even if the same item existed in both collections to start Concat() Combines two collections together to form a superset of both collections Duplicate items are not removed from the resultant collection Intersect() Extracts the collection of items that exist in both original collections Distinct() Filters out duplicate items from a collection so that each item within the resultant collection is unique Standard Query Operators TABLE 14.1: Simpler Standard Query Operators (Continued) Comment Type Description SequenceEquals() Compares two collections and returns a Boolean indicating whether the collections are identical, including the order of items within the collection (This is a very helpful message when testing expected results.) Reverse() Reverses the items within a collection so they occur in reverse order when iterating over the collection Included on System.Linq.Enumerable is a collection of aggregate functions that enumerate the collection and calculate a result Count is one example of an aggregate function already shown within the chapter TABLE 14.2: Aggregate Functions on System.Linq.Enumerable Comment Type Description Count() Provides a total count of the number of items within the collection Average() Calculates the average value for a numeric key selector Sum() Computes the sum values within a numeric collection Max() Determines the maximum value among a collection of numeric values Min() Determines the minimum value among a collection of numeric values Note that each method listed in Tables 14.1 and 14.2 will trigger deferred execution ADVANCED TOPIC Queryable Extensions for IQueryable One virtually identical interface to IEnumerable is IQueryable Because IQueryable derives from IEnumerable, it has all the members 551 552 Chapter 14: Collection Interfaces with Standard Query Operators of IEnumerable but only those declared directly (GetEnumerator(), for example) Extension methods are not inherited, so IQueryable doesn’t have any of the Enumerable extension methods However, it has a similar extending class called System.Linq.Queryable that adds to IQueryable virtually all of the same methods Enumerable added to IEnumerable Therefore, it provides a very similar programming interface What makes IQueryable unique is that it enables custom LINQ providers A LINQ provider subdivides expressions into their constituent parts Once divided, the expression can be translated into another language, serialized for remote execution, injected with an asynchronous execution pattern, and much more Essentially, LINQ providers allow for an interception mechanism into a standard collection API, and via this seemingly limitless functionality, behavior relating to the queries and collection can be injected For example, LINQ providers allow for the translation of a query expression from C# into SQL that is then executed on a remote database In so doing, the C# programmer can remain in her primary object-oriented language and leave the translation to SQL to the underlying LINQ provider Through this type of expression, programming languages are able to span the impedance mismatch between the object-oriented world and the relational database SUMMARY After introducing anonymous types, implicit variables, and collection initializers, this chapter described the internals of how the foreach loop works and what interfaces are required for its execution In addition, developers frequently filter a collection so there are fewer items and project the collection so the items take a different form Toward that end, this chapter discussed the details of how to use the standard query operators, common collection APIs on the System.Linq.Enumerable class, to perform collection manipulation In the introduction to standard query operators, I spent a few pages detailing deferred execution and how developers should take care to avoid unintentionally reexecuting an expression via a subtle call that enumerates Summary over the collection contents The deferred execution and resultant implicit execution of standard query operators is a significant quality, especially when the query execution is expensive Imagine, for example, a LINQ provider that returns data from a database, perhaps a database that is not running on the local computer Rather than retrieve the data from a database regardless of the selection criteria, the lambda expression would provide an implementation of IEnumerable (more likely IQueryable and an expression tree) that possibly includes context information such as the connection string, but not the data itself The data retrieval wouldn’t occur until the call to GetEnumerator() or even MoveNext() However, the GetEnumerator() call is generally implicit, such as when iterating over the collection with foreach or calling an Enumerable method such as Count() or Cast() In such cases, developers need to be aware of the subtle call and avoid repeated calls to any expensive operation that deferred execution might involve For example, if calling GetEnumerator() involves a distributed call over the network to a database, avoid unintentional duplicate calls to iterations with Count() or foreach Listing 14.22 appeared within an Advanced Topic section because of the complexity of calling multiple standard query operators one after the other Although requirements for similar execution may be common, it is not necessary to rely on standard query operators directly C# 3.0 includes query expressions, a SQL-like syntax for manipulating collections in a way that is frequently easier to code and read, as I’ll show in the next chapter 553 This page intentionally left blank 15 Query Expressions 14 showed a query using standard query operators for GroupJoin(), SelectMany(), and Distinct(), in addition to the creation of two anonymous types The result was a statement that spanned multiple lines and was fairly complex to comprehend, certainly much more complex than statements typically written in C# 2.0, even though it appears fully compatible with C# 2.0 syntax The introduction of standard query operators facilitated a far greater likelihood of scenarios where such complex statements were desirable even though the resultant code may not be In addition, the queries that standard query operators implemented were functionally very similar to queries generally implemented in SQL T HE END OF CHAPTER Introducing Query Expressions Query Expressions As Method Invocations Projection Features Filtering Sorting Let Grouping 555 556 Chapter 15: Query Expressions The culmination of these two factors resulted in the C# language designers adding a new syntax to C# 3.0: query expressions With query expressions, many standard query operator statements are transformed into more readable code, code that looks very much like SQL In this chapter, I introduce the new syntax of query expressions and use this syntax to explain how to express many of the queries from the preceding chapter Introducing Query Expressions Besides iterating over all the items within a collection, two of the most frequent operations developers perform is filtering the collection so there are fewer items to iterate over or projecting the collection so the items take a different form For example, given a collection of files, we could filter it vertically to create a new collection of only the files with a “.cs” extension, or only the ten largest files Alternatively, we could project across the file collection to create a new collection of paths to the directories the files are located in and the corresponding directory size There are many ways to perform this type of operation, but one of the easiest was introduced in C# 3.0: query expressions Query expressions generally begin and end with the from and select clauses, which use the contextual keywords from and select (Technically, query expressions can also end with a groupby clause, as shown later in this chapter.) Listing 15.1 shows a query expression example and Output 15.1 shows the results Listing 15.1: Simple Query Expression using System; using System.Collections.Generic; using System.Linq; // static string[] Keywords = { "abstract", "add*", "as", "ascending*", "base", "bool", "break", "by*", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "descending*", Introducing Query Expressions "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "from*", "float", "for", "foreach", "get*", "group*", "goto", "if", "implicit", "in", "int", "into*", "interface", "internal", "is", "lock", "long", "join*", "let*", "namespace", "new", "null", "object", "operator", "orderby*", "out", "override", "params", "partial*", "private", "protected", "public", "readonly", "ref", "remove*", "return", "sbyte", "sealed", "select*", "set*", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "value*", "virtual", "void", "volatile", "where*", "while", "yield*"}; private static void ShowContextualKeywords1() { IEnumerable selection = from word in Keywords where !word.Contains('*') select word; foreach (string keyword in selection) { Console.Write(" " + keyword); } } // OUTPUT 15.1: abstract as base bool break byte case catch char checked class const continue decimal default delegate double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while In this query expression, selection is assigned the collection of C# keywords but not contextual keywords The query expression in this example includes a where clause that filters out the noncontextual keywords Developers familiar with SQL will notice that query expressions have a syntax that is similar to that of SQL so as to be familiar to the thousands of programmers who know SQL In spite of the similarities, however, there are some obvious inconsistencies The most notable of these is that rather 557 558 Chapter 15: Query Expressions than starting an expression with select, as SQL so often does, C# query expressions begin with the contextual keyword from The reason for this is to enable IntelliSense, or the ability to predict the members on the objects being selected For example, because from appears first and identifies the string array Keywords as the data source, the code editor knows that word is of type string This enables IntelliSense—member access (a dot operation) on word will display only the members of string If the from clause appeared after the select, any dot operations prior to the from clause would not know what the data type of word was and, therefore, would not be able to display a list of word’s members In Listing 15.1, for example, it wouldn’t be possible to predict that Contains() was a possible member of word word is referred to as a range variable; it represents each item in the collection Projection The output of a query expression is an IEnumerable or IQueryable collection.1 The data type of T is inferred from the select or groupby clause In Listing 15.1, for example, the data type of string is inferred from select word because word is a string word’s data type is the type argument of the IEnumerable collection in the from clause Since Keywords is a string array, it implements IEnumerable, and therefore, word is a string The type resulting from an expression that queries a collection of a certain type is by no means limited to be a sequence of that original type Rather, the select clause allows for projection of data into an entirely different type Consider the query expression in Listing 15.2, and its corresponding output in Output 15.2 Listing 15.2: Projection Using Query Expressions using using using using System; System.Collections.Generic; System.Linq; System.IO; Query expression output is practically always IEnumerable, but theoretically, not necessarily Nothing is stopping anyone from coming up with an implementation of the query operators that returns something else To so would be somewhat perverse, but there is no requirement in the language that query operators return IEnumerable Introducing Query Expressions // static void List1(string rootDirectory, string searchPattern) { IEnumerable files = from fileName in Directory.GetFiles( rootDirectory, searchPattern) select new FileInfo(fileName); foreach (FileInfo file in files) { // As simplification, current directory is // assumed to be a subdirectory of // rootDirectory string relativePath = file.FullName.Substring( Environment.CurrentDirectory.Length); Console.WriteLine(".{0}({1})", relativePath, file.LastWriteTime); } } // OUTPUT 15.2: \TestData\Account.cs(11/22/2007 11:56:11 AM) \TestData\Bill.cs(8/10/2007 9:33:55 PM) \TestData\Contact.cs(8/19/2007 11:40:30 PM) \TestData\Customer.cs(11/17/2007 2:02:52 AM) \TestData\Employee.cs(8/17/2007 1:33:22 AM) \TestData\Person.cs(10/22/2007 10:00:03 PM) Notice that this query expression returns an IEnumerable rather than the IEnumerable data type returned by System.IO.Directory.GetFiles() The select clause of the query expression can potentially project out a data type that is different from what was collected by the from clause expression (Directory.GetFiles()) In fact, projection such as this is the key driving factor for why C# 3.0 includes anonymous types within the language Via anonymous types, it becomes possible to select out the exact data you seek without having to define an explicit type For example, Listing 15.3 provides output similar to that in Listing 15.2, but via anonymous types rather than FileInfo 559 560 Chapter 15: Query Expressions Listing 15.3: Anonymous Types within Query Expressions using using using using System; System.Collections.Generic; System.Linq; System.IO; // static void List2(string rootDirectory, string searchPattern) { var files = from fileName in Directory.GetFiles( rootDirectory, searchPattern) select new { Name = fileName, LastWriteTime = File.GetLastWriteTime(fileName) }; foreach (var file in files) { Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime); } } // In this example, the query projects out only the filename and its last file write time A projection such as the one in Listing 15.3 makes little difference when working with something small such as FileInfo However, horizontal projection that filters down the amount of data associated with each item in the collection is extremely powerful when the amount of data is significant and retrieving it (perhaps from a different computer over the Internet) is expensive Rather than retrieving all the data, the use of anonymous types enables the capability of storing only the required data into the collection Imagine, for example, a large database that has tables with 30 or more columns Because parts of the data are required in some scenarios but not others, without anonymous types it is necessary to define classes with all the data points that can be reused throughout, or alternatively, to define multiple classes specialized for each scenario Instead, anonymous types enable support for types to be defined on the fly—types that contain only the data needed for their immediate scenario Other scenarios can have a different projection of only the properties needed for that scenario Introducing Query Expressions BEGINNER TOPIC Deferred Execution with Query Expressions The topic of deferred execution appeared in the preceding chapter as well, and the same principles apply to query expressions Consider again the assignment of selection in Listing 15.1 The assignment itself does not execute the query expression In other words, during the assignment of selection, word.Contains("*") is not called Rather, the query expression saves off the selection criteria to be used when iterating over the collection identified by the selection variable To demonstrate this point, consider Listing 15.4 and the corresponding output (Output 15.3) Listing 15.4: Deferred Execution and Query Expressions (Example 1) using System; using System.Collections.Generic; using System.Linq; // private static void ShowContextualKeywords2() { IEnumerable selection = from word in Keywords where IsKeyword(word) select word; foreach (string keyword in selection) { Console.Write(keyword); } } // Side effect (console output) included in predicate to show // deferred execution not as a best practice private static bool IsKeyword(string word) { if (word.Contains('*')) { Console.Write(" "); return true; } else { return false; } 561 562 Chapter 15: Query Expressions } // OUTPUT 15.3: add* ascending* by* descending* from* get* group* into* join* let* orderby* partial* remove* select* set* value* where* yield* Notice that in Listing 15.4, no space is output within the foreach loop The space between the contextual keywords is output in the IsKeyword() function, demonstrating that the IsKeyword() function isn’t called until the code iterates over selection rather than when selection is assigned The point is that although selection is a collection (it is of type IEnumerable after all), at the time of assignment everything following the from clause comprises the selection criteria Not until we begin to iterate over selection are the criteria applied Consider a second example (see Listing 15.5 and Output 15.4) Listing 15.5: Deferred Execution and Query Expressions (Example 2) using System; using System.Collections.Generic; using System.Linq; // private static void CountContextualKeywords() { int delegateInvocations = 0; Func func = text=> { delegateInvocations++; return text; }; IEnumerable selection = from keyword in Keywords where keyword.Contains('*') select func(keyword); Console.WriteLine( "1 delegateInvocations={0}", delegateInvocations); Introducing Query Expressions // Executing count should invoke func once for // each item selected Console.WriteLine( "2 Contextual keyword count={0}", selection.Count()); Console.WriteLine( "3 delegateInvocations={0}", delegateInvocations); // Executing count should invoke func once for // each item selected Console.WriteLine( "4 Contextual keyword count={0}", selection.Count()); Console.WriteLine( "5 delegateInvocations={0}", delegateInvocations); // Cache the value so future counts will not trigger // another invocation of the query List selectionCache = selection.ToList(); Console.WriteLine( "6 delegateInvocations={0}", delegateInvocations); // Retrieve the count from the cached collection Console.WriteLine( "7 selectionCache count={0}",selectionCache.Count()); Console.WriteLine( "8 delegateInvocations={0}", delegateInvocations); } // OUTPUT 15.4: delegateInvocations=0 Contextual keyword count=15 delegateInvocations=15 Contextual keyword count=15 delegateInvocations=30 delegateInvocations=45 selectionCache count=15 delegateInvocations=45 Rather than defining a separate method, Listing 15.5 uses an anonymous method that counts the number of times the method is called 563 ... OUTPUT 14.1: Bifocals ( 178 4) Phonograph ( 178 4) { Title = Bifocals, YearOfPublication = 178 4 } { Title = Phonograph, YearOfPublication = 1 877 } { Title = Bifocals, Year = 178 4 } 509 510 Chapter... long[] {7} }, }; } Listing 14.11 also provides a selection of sample data Output 14.2 displays the results OUTPUT 14.2: Bifocals( 178 4) Phonograph(1 877 ) Kinetoscope(1888) Electrical Telegraph(18 37) ... Therefore, the C# compiler generates only one data type for these two anonymous declarations patent3, however, forces the compiler to create a second anonymous type because the property name for

Ngày đăng: 12/08/2014, 16:21

Từ khóa liên quan

Mục lục

  • 13 Events

    • Coding the Observer Pattern with Multicast Delegates

      • Defining Subscriber Methods

      • Defining the Publisher

      • Hooking Up the Publisher and Subscribers

      • Invoking a Delegate

      • Check for null

      • Delegate Operators

      • Sequential Invocation

      • Error Handling

      • Method Returns and Pass-By-Reference

      • Events

        • Why Events?

        • Declaring an Event

        • Coding Conventions

        • Generics and Delegates

        • Customizing the Event Implementation

        • 14 Collection Interfaces with Standard Query Operators

          • Anonymous Types and Implicit Local Variable Declaration

            • Anonymous Types

            • Implicitly Typed Local Variables

            • More about Anonymous Types and Implicit Local Variables

            • Collection Initializers

            • What Makes a Class a Collection: IEnumerable<T>

              • foreach with Arrays

              • foreach with IEnumerable<T>

Tài liệu cùng người dùng

Tài liệu liên quan