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

Essential CSharp 3rd Edition_5 pptx

98 244 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 98
Dung lượng 1,86 MB

Nội dung

ptg Anonymous Types and Implicitly Typed Local Variables 541 Listing 14.2: Type Safety and Immutability of Anonymous Types class Program { static void Main() { var patent1 = new { }; var patent2 = new { }; var patent3 = new { patent1.Title, }; // ERROR: Cannot implicitly convert type // 'AnonymousType#1' to 'AnonymousType#2' patent1 = patent2; // ERROR: Cannot implicitly convert type // 'AnonymousType#3' to 'AnonymousType#2' patent1 = patent3; // ERROR: Property or indexer 'AnonymousType#1.Title' // cannot be assigned to it is read only' patent1.Title = "Swiss Cheese"; } } The resultant two compile errors assert the fact that the types are not com- patible, so they will not successfully convert from one to the other. The third compile error is caused by the reassignment of the Title property. Anonymous types are immutable, so it is a compile error to change a property on an anonymous type once it has been instantiated. Although not shown in Listing 14.2, it is not possible to declare a method with an implicit data type parameter (var). Therefore, instances Title = "Bifocals", YearOfPublication = "1784" YearOfPublication = "1877", Title = "Phonograph" Year = patent1.YearOfPublication From the Library of Wow! eBook ptg Chapter 14: Collection Interfaces with Standard Query Operators542 of anonymous types can only be passed outside the method in which they are created in only two ways. First, if the method parameter is of type object, the anonymous type instance may pass outside the method because the anonymous type will convert implicitly. A second way is to use method type inference, whereby the anonymous type instance is passed as a method type parameter that the compiler can successfully infer. Calling void Method<T>(T parameter) using Function(patent1), therefore, would succeed, although the available operations on parameter within Function() are limited to those supported by object. In spite of the fact that C# allows anonymous types such as the ones shown in Listing 14.1, it is generally not recommended that you define them in this way. Anonymous types provide critical functionality with C# 3.0 support for projections, such as joining/associating collections, as we discuss later in the chapter. However, generally you should reserve anony- mous type definitions for circumstances where they are required, such as aggregation of data from multiple types. ADVANCED TOPIC Anonymous Type Generation Even though Console.WriteLine()’s implementation is to call ToString(), notice in Listing 14.1 that the output from Console.WriteLine() is not the default ToString(), which writes out the fully qualified data type name. Rather, the output is a list of PropertyName = value pairs, one for each property on the anonymous type. This occurs because the compiler over- rides ToString() in the anonymous type code generation, and instead for- mats the ToString() output as shown. Similarly, the generated type includes overriding implementations for Equals() and GetHashCode(). The implementation of ToString() on its own is an important reason that variance in the order of properties causes a new data type to be gener- ated. If two separate anonymous types, possibly in entirely separate types and even namespaces, were unified and then the order of properties changed, changes in the order of properties on one implementation would have noticeable and possibly unacceptable effects on the others’ ToString() From the Library of Wow! eBook ptg Collection Initializers 543 results. Furthermore, at execution time it is possible to reflect back on a type and examine the members on a type—even to call one of these members dynamically (determining at runtime which member to call). A variance in the order of members on two seemingly identical types could trigger unex- pected results, and to avoid this, the C# designers decided to generate two different types. Collection Initializers Another feature added to C# in version 3.0 was collection initializers. A collection initializer allows programmers to construct a collection with an initial set of members at instantiation time in a manner similar to array declaration. Without collection initialization, elements had to be explicitly added to a collection after the collection was instantiated—using some- thing like System.Collections.Generic.ICollection<T>’s Add() method. With collection initialization, the Add() calls are generated by the C# com- plier rather than explicitly coded by the developer. Listing 14.3 shows how to initialize the collection using a collection initializer instead. Listing 14.3: Filtering with System.Linq.Enumerable.Where() using System; using System.Collections.Generic; class Program { static void Main() { List<string> sevenWorldBlunders; sevenWorldBlunders = new List<string>() { // Quotes from Ghandi "Wealth without work", "Pleasure without conscience", "Knowledge without character", "Commerce without morality", "Science without humanity", "Worship without sacrifice", "Politics without principle" }; From the Library of Wow! eBook ptg Chapter 14: Collection Interfaces with Standard Query Operators544 Print(sevenWorldBlunders); } private static void Print<T>(IEnumerable<T> items) { foreach (T item in items) { Console.WriteLine(item); } } } The syntax is similar not only to the array initialization, but also to an object initializer with the curly braces following the constructor. If no parameters are passed in the constructor, the parentheses following the data type are optional (as they are with object initializers). A few basic requirements are needed in order for a collection initializer to compile successfully. Ideally, the collection type to which a collection ini- tializer is applied would be of a type that implements System.Collec- tions.Generic.ICollection<T>. This ensures that the collection includes an Add() that the compiler-generated code can invoke. However, a relaxed version of the requirement also exists and simply demands that one or more Add() methods exist on a type that implements IEnumerable<T>—even if the collection doesn’t implement ICollection<T>. The Add() methods need to take parameters that are compatible with the values specified in the col- lection initializer. Allowing initializers on collections that don’t support ICollection<T> was important for two reasons. First, it turns out that the majority of collec- tions (types that implement IEnumerable<T>) do not also implement ICollection<T>, thus significantly reducing the usefulness of collection initializers. Second, matching on the method name and signature compatibility with the collection initialize items enables greater diversity in the items ini- tialized into the collection. For example, the initializer now can support new DataStore(){ a, {b, c}} as long as there is one Add() method whose signature is compatible with a and a second Add() method compatible with b, c. From the Library of Wow! eBook ptg Collection Initializers 545 Note that you cannot have a collection initializer for an anonymous type since the collection initializer requires a constructor call, and it is impossible to name the constructor. The workaround is to define a method such as static List<T> CreateList<T>(T t) { return new List<T>(); }. Method type inference allows the type parameter to be implied rather than specified explicitly, and so this workaround successfully allows for the creation of a collection of anonymous types. Another approach to initializing a collection of anonymous types is to use an array initializer. Since it is not possible to specify the data type in the constructor, array initialization syntax allows for anonymous array ini- tializers using new[] (see Listing 14.4). Listing 14.4: Initializing Anonymous Type Arrays using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { new { TeamName = "France", Players = new string[] { "Fabien Barthez", "Gregory Coupet", "Mickael Landreau", "Eric Abidal", // } }, new { TeamName = "Italy", Players = new string[] { "Gianluigi Buffon", "Angelo Peruzzi", "Marco Amelia", "Cristian Zaccardo", // } } }; var worldCup2006Finalists = new[] { From the Library of Wow! eBook ptg Chapter 14: Collection Interfaces with Standard Query Operators546 Print(worldCup2006Finalists); } private static void Print<T>(IEnumerable<T> items) { foreach (T item in items) { Console.WriteLine(item); } } } The resultant variable is an array of the anonymous type items, which must be homogenous since it is an array. What Makes a Class a Collection: IEnumerable<T> By definition, a collection within .NET is a class that, at a minimum, imple- ments IEnumerable<T> (technically, it would be the nongeneric type IEnu- merable). This interface is a key because implementing the methods of IEnumerable<T> is the minimum implementation requirement needed to support iterating over the collection. Chapter 3 showed how to use a foreach statement to iterate over an array of elements. The syntax is simple and avoids the complication of having to know how many elements there are. The runtime does not directly support the foreach statement, however. Instead, the C# compiler transforms the code as described in this section. foreach with Arrays Listing 14.5 demonstrates a simple foreach loop iterating over an array of integers and then printing out each integer to the console. Listing 14.5: foreach with Arrays int[] array = new int[]{1, 2, 3, 4, 5, 6}; foreach (int item in array) { Console.WriteLine(item); } From the Library of Wow! eBook ptg What Makes a Class a Collection: IEnumerable<T> 547 From this code, the C# compiler creates a CIL equivalent of the for loop, as shown in Listing 14.6. Listing 14.6: Compiled Implementation of foreach with Arrays int number; int[] tempArray; int[] array = new int[]{1, 2, 3, 4, 5, 6}; tempArray = array; for (int counter = 0; (counter < tempArray.Length); counter++) { int item = tempArray[counter]; Console.WriteLine(item); } In this example, note that foreach relies on support for the Length property and the index operator ([]). With the Length property, the C# compiler can use the for statement to iterate through each element in the array. foreach with IEnumerable<T> Although the code shown in Listing 14.6 works well on arrays where the length is fixed and the index operator is always supported, not all types of collections have a known number of elements. Furthermore, many of the col- lection classes, including the Stack<T>, Queue<T>, and Dictionary<Tkey, Tvalue> classes, do not support retrieving elements by index. Therefore, a more general approach of iterating over collections of elements is needed. The iterator pattern provides this capability. Assuming you can determine the first, next, and last elements, knowing the count and supporting retrieval of elements by index is unnecessary. The System.Collections.Generic.IEnumerator<T> and nongeneric System.Collections.IEnumerator interfaces (see Listing 14.8) are designed to enable the iterator pattern for iterating over collections of elements, rather than the length-index pattern shown in Listing 14.6. A class diagram of their relationships appears in Figure 14.1. From the Library of Wow! eBook ptg Chapter 14: Collection Interfaces with Standard Query Operators548 IEnumerator, which IEnumerator<T> derives from, includes three members. The first is bool MoveNext(). Using this method, you can move from one element within the collection to the next while at the same time detecting when you have enumerated through every item. The second member, a read-only property called Current, returns the element cur- rently in process. Current is overloaded in IEnumerator<T>, providing a type-specific implementation of it. With these two members on the collec- tion class, it is possible to iterate over the collection simply using a while loop, as demonstrated in Listing 14.7. (The Reset() method usually throws a NotImplementedException and, therefore, should never be called. If you need to restart an enumeration, just create a fresh enumerator.) Listing 14.7: Iterating over a Collection Using while System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>(); int number; // // This code is conceptual, not the actual code. while (stack.MoveNext()) { number = stack.Current; Console.WriteLine(number); } Figure 14.1: IEnumerator<T> and IEnumerator Interfaces IDisposable IEnumerator Interface Interface IEnumerable Interface IEnumerableϽTϾ Generic Interface IEnumeratorϽTϾ Generic Interface Methods Methods Methods IEnumerable Methods Properties Properties IDisposable IEnumerator Dispose Current Current MoveNext Reset GetEnumerator GetEnumerator From the Library of Wow! eBook ptg What Makes a Class a Collection: IEnumerable<T> 549 In Listing 14.7, the MoveNext() method returns false when it moves past the end of the collection. This replaces the need to count elements while looping. Listing 14.7 uses a System.Collections.Generic.Stack<T> as the col- lection type. Numerous other collection types exist; this is just one exam- ple. The key trait of Stack<T> is its design as a last in, first out (LIFO) collection. It is important to note that the type parameter T identifies the type of all items within the collection. Collecting one particular type of object within a collection is a key characteristic of a generic collection. It is important that the programmer understands the data type within the collection when adding, removing, or accessing items within the collection. This preceding example shows the gist of the C# compiler output, but it doesn’t actually compile that way because it omits two important details concerning the implementation: interleaving and error handling. State Is Shared The problem with an implementation such as Listing 14.7 is that if two such loops interleaved each other—one foreach inside another, both using the same collection—the collection must maintain a state indicator of the current element so that when MoveNext() is called, the next ele- ment can be determined. The problem is that one interleaving loop can affect the other. (The same is true of loops executed by multiple threads.) To overcome this problem, the collection classes do not support IEnu- merator<T> and IEnumerator interfaces directly. As shown in Figure 14.1, there is a second interface, called IEnumerable<T>, whose only method is GetEnumerator(). The purpose of this method is to return an object that supports IEnumerator<T>. Instead of the collection class maintaining the state, a different class, usually a nested class so that it has access to the internals of the collection, will support the IEnumerator<T> interface and will keep the state of the iteration loop. The enumerator is like a “cursor” or a “bookmark” in the sequence. You can have multiple bookmarks, and moving each of them enumerates over the collection independently of the other. Using this pattern, the C# equivalent of a foreach loop will look like the code shown in Listing 14.8. From the Library of Wow! eBook ptg Chapter 14: Collection Interfaces with Standard Query Operators550 Listing 14.8: A Separate Enumerator Maintaining State during an Iteration System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>(); int number; System.Collections.Generic.Stack<int>.Enumerator enumerator; // // If IEnumerable<T> is implemented explicitly, // then a cast is required. // ((IEnumerable<int>)stack).GetEnumerator(); enumerator = stack.GetEnumerator(); while (enumerator.MoveNext()) { number = enumerator.Current; Console.WriteLine(number); } Cleaning Up Following Iteration Since the classes that implement the IEnumerator<T> interface maintain the state, sometimes you need to clean up the state after it exits the loop (because either all iterations have completed or an exception is thrown). To achieve this, the IEnumerator<T> interface derives from IDisposable. Enu- merators that implement IEnumerator do not necessarily implement IDis- posable, but if they do, Dispose() will be called as well. This enables the calling of Dispose() after the foreach loop exits. The C# equivalent of the final CIL code, therefore, looks like Listing 14.9. Listing 14.9: Compiled Result of foreach on Collections System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>(); System.Collections.Generic.Stack<int>.Enumerator enumerator; IDisposable disposable; enumerator = stack.GetEnumerator(); try { int number; while (enumerator.MoveNext()) { number = enumerator.Current; Console.WriteLine(number); } } From the Library of Wow! eBook [...]... FileName FileName FileName = = = = = = = = = = AssemblyInfo.cs, Size = 1704 } CodeAnalysisRules.xml, Size = 735 } CustomDictionary.xml, Size = 199 } EssentialCSharp.sln, Size = 40415 } EssentialCSharp.suo, Size = 454656 } EssentialCSharp.vsmdi, Size = 499 } EssentialCSharp.vssscc, Size = 256 } intelliTechture.ConsoleTester.dll, Size = 24576 } intelliTechture.ConsoleTester.pdb, Size = 30208 } LocalTestRun.testrunconfig, . Size = 7 35 } { FileName = CustomDictionary.xml, Size = 199 } { FileName = EssentialCSharp.sln, Size = 404 15 } { FileName = EssentialCSharp.suo, Size = 454 656 } { FileName = EssentialCSharp.vsmdi,. EssentialCSharp.vsmdi, Size = 499 } { FileName = EssentialCSharp.vssscc, Size = 256 } { FileName = intelliTechture.ConsoleTester.dll, Size = 2 457 6 } { FileName = intelliTechture.ConsoleTester.pdb,. Patent[] Patents = new Patent[] { From the Library of Wow! eBook ptg Standard Query Operators 55 5 new Patent(){ Title="Bifocals", YearOfPublication="1784", InventorIds=new

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