Essential CSharp 3rd Edition_6

Chia sẻ: Up Upload | Ngày: | Loại File: PDF | Số trang:98

0
25
lượt xem
3
download

Essential CSharp 3rd Edition_6

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Tham khảo tài liệu 'essential csharp 3rd edition_6', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:
Lưu

Nội dung Text: Essential CSharp 3rd Edition_6

  1. 639 I terators CSharpPrimitiveTypes primitives = new CSharpPrimitiveTypes(); foreach (string primitive in primitives) { Console.WriteLine(primitive); } } } The results of Listing 16.13 appear in Output 16.5. OUTPUT 16.5: object byte uint ulong float char bool ushort decimal int sbyte short long void double string The output from this listing is a listing of the C# primitive types.1 Iterators and State When an iterator is first called in a foreach statement (such as foreach (string primitive in primitives) in Listing 16.13), its state is initialized within the enumerator. The iterator maintains its state as long as the foreach statement at the call site continues to execute. When you yield a value, process it, and resume the foreach statement at the call site, the iter- ator continues where it left off the previous time around the loop and 1. In alpha versions of the C# 2.0 compiler, yield was a keyword rather than a contextual keyword. However, such a change could result in an incompatibility between C# 1.0 and C# 2.0. Instead, yield became a contextual keyword that must appear before return. As a result, no code-breaking change occurred because C# 1.0 did not allow any text (besides comments) prior to the return keyword. From the Library of Wow! eBook
  2. 640 C hapter 16: Building Custom Collections continues processing. When the foreach statement at the call site termi- nates, the iterator’s state is no longer saved. It is always safe to call the iter- ator again since the generated code never resets the state of the iterator but instead creates a new one when needed. Figure 16.8 shows a high-level sequence diagram of what takes place. Remember that the MoveNext() method appears on the IEnumerator interface. primitives: enumerator: Program Console CSharpPrimitiveTypes Enumerator GetEnumerator() Instantiate MoveNext() yield return "object" WriteLine() MoveNext() yield return "byte" WriteLine() ... MoveNext() yield return "string" WriteLine() Figure 16.8: Sequence Diagram with yield return From the Library of Wow! eBook
  3. 641 I terators In Listing 16.13, the foreach statement at the call site initiates a call to GetEnumerator() on the CSharpPrimitiveTypes instance called primi- tives. Given the iterator instance (referenced by iterator), foreach begins each iteration with a call to MoveNext(). Within the iterator, you yield a value back to the foreach statement at the call site. After the yield return statement, the GetEnumerator() method seemingly pauses until the next MoveNext() request. Back at the call site, the foreach statement displays the yielded value on the screen. It then loops back around and calls MoveNext() on the iterator again. Notice that the second time, processing picks up at the second yield return statement. Once again, the foreach displays on the screen what CSharpPrimitiveTypes yielded and starts the loop again. This process continues until there are no more yield return statements within the iterator. At that point, the foreach loop at the call site terminates. More Iterator Examples Before you modify BinaryTree, you must modify Pair to support the IEnumerable interface using an iterator. Listing 16.14 is an example that yields each element in Pair. Listing 16.14: Using yield to Implement BinaryTree public struct Pair: IPair, IEnumerable { public Pair(T first, T second) { _first = first; _second = second; } public T First { get{ return _first; } private set{ _first = value; } } private T _first; public T Second { get{ return _second; } private set{ _second = value; } } From the Library of Wow! eBook
  4. 642 C hapter 16: Building Custom Collections private T _second; #region IEnumerable public IEnumerator GetEnumerator() { yield return First; yield return Second; } #endregion IEnumerable #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator { return GetEnumerator(); } #endregion } In Listing 16.14, the iteration over the Pair data type loops twice: first through yield return First, and then through yield return Second. Each time the yield return statement within GetEnumerator() is encoun- tered, the state is saved and execution appears to “jump” out of the GetEnumerator() method context and into the context of the call site. When the second iteration starts, GetEnumerator() begins to execute again with the yield return Second statement. System.Collections.Generic.IEnumerable inherits from System. Collections.IEnumerable. Therefore, when implementing IEnumera- ble, it is also necessary to implement IEnumerable. In Listing 16.14, you do so explicitly, and the implementation simply involves a call to IEnumera- ble’s GetEnumerator() implementation. This call from IEnumerable. GetEnumerator() to IEnumerable.GetEnumerator() will always work because of the type compatibility (via inheritance) between IEnumerable and IEnumerable. Since the signatures for both GetEnumerator()s are identical (the return type does not distinguish a signature), one or both implementations must be explicit. Given the additional type safety offered by IEnumerable’s version, you implement IEnumerable’s implementa- tion explicitly. Listing 16.15 uses the Pair.GetEnumerator() method and displays "Inigo" and "Montoya" on two consecutive lines. From the Library of Wow! eBook
  5. 643 I terators Listing 16.15: Using Pair.GetEnumerator() via foreach Pair fullname = new Pair("Inigo", "Montoya"); foreach (string name in fullname) { Console.WriteLine(name); } Notice that the call to GetEnumerator() is implicit within the foreach loop. Placing a yield return within a Loop It is not necessary to hardcode each yield return statement, as you did in both CSharpPrimitiveTypes and Pair. Using the yield return state- ment, you can return values from inside a loop construct. Listing 16.16 uses a foreach loop. Each time the foreach within GetEnumerator() exe- cutes, it returns the next value. Listing 16.16: Placing yield return Statements within a Loop public class BinaryTree: IEnumerable { // ... #region IEnumerable public IEnumerator GetEnumerator() { // Return the item at this node. yield return Value; // Iterate through each of the elements in the pair. foreach (BinaryTree tree in SubItems) { if (tree != null) { // Since each element in the pair is a tree, // traverse the tree and yield each // element. foreach (T item in tree) { yield return item; } } } } #endregion IEnumerable From the Library of Wow! eBook
  6. 644 C hapter 16: Building Custom Collections #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } In Listing 16.16, the first iteration returns the root element within the binary tree. During the second iteration you traverse the pair of subele- ments. If the subelement pair contains a non-null value, then you traverse into that child node and yield its elements. Note that foreach(T item in tree) is a recursive call to a child node. As observed with CSharpPrimitiveTypes and Pair, you can now iterate over BinaryTree using a foreach loop. Listing 16.17 demon- strates this, and Output 16.6 shows the results. Listing 16.17: Using foreach with BinaryTree // JFK jfkFamilyTree = new BinaryTree( "John Fitzgerald Kennedy"); jfkFamilyTree.SubItems = new Pair( new BinaryTree("Joseph Patrick Kennedy"), new BinaryTree("Rose Elizabeth Fitzgerald")); // Grandparents (Father's side) jfkFamilyTree.SubItems.First.SubItems = new Pair( new BinaryTree("Patrick Joseph Kennedy"), new BinaryTree("Mary Augusta Hickey")); // Grandparents (Mother's side) jfkFamilyTree.SubItems.Second.SubItems = new Pair( new BinaryTree("John Francis Fitzgerald"), new BinaryTree("Mary Josephine Hannon")); foreach (string name in jfkFamilyTree) { Console.WriteLine(name); } From the Library of Wow! eBook
  7. 645 I terators OUTPUT 16.6: John Fitzgerald Kennedy Joseph Patrick Kennedy Patrick Joseph Kennedy Mary Augusta Hickey Rose Elizabeth Fitzgerald John Francis Fitzgerald Mary Josephine Hannon BEGINNER TOPIC struct versus class An interesting side effect of defining Pair as a struct rather than a class is that SubItems.First and SubItems.Second cannot be assigned directly. The following will produce a compile error indicating that Sub- Items cannot be modified, “because it is not a variable”: jfkFamilyTree.SubItems.First = new BinaryTree("Joseph Patrick Kennedy"); The issue is that SubItems is a property of type Pair, a struct. There- fore, when the property returns the value, a copy of _SubItems is made, and assigning First on a copy that is promptly lost at the end of the state- ment would be misleading. Fortunately, the C# compiler prevents this. To overcome the issue, don’t assign it (see the approach in Listing 16.17), use class rather than struct for Pair, don’t create a SubItems property and instead use a field, or provide properties in BinaryTree that give direct access to _SubItems members. Canceling Further Iteration: yield break Sometimes you might want to cancel further iteration. You can do this by including an if statement so that no further statements within the code are executed. However, you can also jump back to the call site, causing MoveNext() to return false. Listing 16.18 shows an example of such a method. From the Library of Wow! eBook
  8. 646 C hapter 16: Building Custom Collections Listing 16.18: Escaping Iteration via yield break public System.Collections.Generic.IEnumerable GetNotNullEnumerator() { if((First == null) || (Second == null)) { yield break; } yield return Second; yield return First; } This method cancels the iteration if either of the elements in the Pair class is null. A yield break statement is similar to placing a return statement at the top of a function when it is determined that there is no work to do. It is a way to exit from further iterations without surrounding all remaining code with an if block. As such, it allows multiple exits, and therefore, you should use it with caution because casual reading of the code may miss the early exit. ADVANCED TOPIC How Iterators Work When the C# compiler encounters an iterator, it expands the code into the appropriate CIL for the corresponding enumerator design pattern. In the generated code, the C# compiler first creates a nested private class to imple- ment the IEnumerator interface, along with its Current property and a MoveNext() method. The Current property returns a type corresponding to the return type of the iterator. Listing 16.14 of Pair contains an iterator that returns a T type. The C# compiler examines the code contained within the iterator and creates the necessary code within the MoveNext method and the Current property to mimic its behavior. For the Pair iterator, the C# compiler generates roughly equivalent code (see Listing 16.19). Listing 16.19: C# Equivalent of Compiler-Generated C# Code for Iterators using System; using System.Collections.Generic; From the Library of Wow! eBook
  9. 647 I terators public class Pair : IPair, IEnumerable { // ... // The iterator is expanded into the following // code by the compiler public virtual IEnumerator GetEnumerator() { __ListEnumerator result = new __ListEnumerator(0); result._Pair = this; return result; } public virtual System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new GetEnumerator(); } private sealed class __ListEnumerator : IEnumerator { public __ListEnumerator(int itemCount) { _ItemCount = itemCount; } Pair _Pair; T _Current; int _ItemCount; public object Current { get { return _Current; } } public bool MoveNext() { switch (_ItemCount) { case 0: _Current = _Pair.First; _ItemCount++; return true; case 1: _Current = _Pair.Second; _ItemCount++; From the Library of Wow! eBook
  10. 648 C hapter 16: Building Custom Collections return true; default: return false; } } } } Because the compiler takes the yield return statement and generates cla- sses that correspond to what you probably would have written manually, iterators in C# exhibit the same performance characteristics as classes that implement the enumerator design pattern manually. Although there is no performance improvement, the programmer productivity gained is significant. Creating Multiple Iterators in a Single Class Previous iterator examples implemented IEnumerable.GetEnumera- tor(). This is the method that foreach seeks implicitly. Sometimes you might want different iteration sequences, such as iterating in reverse, fil- tering the results, or iterating over an object projection other than the default. You can declare additional iterators in the class by encapsulating them within properties or methods that return IEnumerable or IEnu- merable. If you want to iterate over the elements of Pair in reverse, for example, you provide a GetReverseEnumerator() method, as shown in Listing 16.20. Listing 16.20: Using yield return in a Method That Returns IEnumerable public struct Pair: IEnumerable { ... public IEnumerable GetReverseEnumerator() { yield return Second; yield return First; } ... } public void Main() { Pair game = new Pair("Redskins", "Eagles"); From the Library of Wow! eBook
  11. 649 I terators foreach (string name in game.GetReverseEnumerator()) { Console.WriteLine(name); } } Note that you return IEnumerable, not IEnumerator. This is differ- ent from IEnumerable.GetEnumerator(), which returns IEnumerator. The code in Main() demonstrates how to call GetReverseEnumerator() using a foreach loop. yield Statement Characteristics You can declare the yield return statement only in members that return an IEnumerator or IEnumerable type, or their nongeneric equiva- lents. More specifically, you can use yield only in GetEnumerator() meth- ods that return IEnumerator, or in methods that return IEnumerable but are not called GetEnumerator(). Methods that include a yield return statement may not have a simple return. If the method uses the yield return statement, then the C# com- piler generates the necessary code to maintain the state machine for the iterator. In contrast, if the method uses the return statement instead of yield return, the programmer is responsible for maintaining his own state machine and returning an instance of one of the iterator interfaces. Further, just as all code paths in a method with a return type must contain a return statement accompanied by a value (assuming they don’t throw an exception), all code paths in an iterator must contain a yield return state- ment if they are to return any data. Additional restrictions on the yield statement that result in compiler errors are as follows. • The yield statement may not appear outside a method, operator, or property accessor. • The yield statement may not appear in an anonymous method (see Chapter 12). • The yield statement may not appear inside the catch and finally clauses of the try statement. Furthermore, a yield statement may appear in a try block only if there is no catch block. From the Library of Wow! eBook
  12. 650 C hapter 16: Building Custom Collections SUMMARY The generic collection classes and interfaces made available in C# 2.0 are universally superior to their nongeneric counterparts; by avoiding boxing penalties and enforcing type rules at compile time, they are faster and safer. Unless you are limited to C# 1.0, you should consider the entire namespace of System.Collections as obsolete (in fact, it has been excluded from the Silverlight CLR entirely). In other words, don’t go back and necessarily remove all code that already uses this namespace. Instead, use System.Collections.Generics for any new code and, over time, con- sider migrating existing code to use the corresponding generic collections which contain both the interfaces and the classes for working with collec- tions of objects. Providing the System.Collections.Generic namespace is not the only change that C# 2.0 brought to collections. Another significant addition is the iterator. Iterators involve a new contextual keyword, yield, that C# uses to generate underlying CIL code that implements the iterator pattern used by the foreach loop. From the Library of Wow! eBook
  13. 17 Reflection, Attributes, and Dynamic Programming of inserting additional metadata into an TTRIBUTES ARE A MEANS A assembly and associating the metadata with a programming con- struct such as a class, method, or property. This chapter investigates the details surrounding attributes that are built into the framework, as well as how to define custom attributes. In order to take advantage of custom attributes, it is necessary to identify them. This is handled through reflec- tion. This chapter begins with a look at reflection, including how you can use it to dynamically bind at runtime and call a member using its name at compile time. This is frequently performed within tools such as a code gen- erator. In addition, reflection is used at execution time when the call target is unknown. Accessing 1 Metadata 8 Dynamic Programming GetType() typeof() Predefined AttributeUsageAttribute 7 Attributes ConditionalAttribute 2 Member Invocation ObsoleteAttribute Reflection, Serialization Attributes, and Dynamic 3 Reflection on Generics 6 Named Parameters Programming 4 Custom Attributes 5 Attribute Constructors 651 From the Library of Wow! eBook
  14. 652 C hapter 17: Reflection, Attributes, and Dynamic Programming The chapter ends with a discussion of dynamic programming, a feature added in C# 4.0 that greatly simplifies working with data that is dynamic and requires execution-time rather than compile-time binding. Reflection Using reflection, it is possible to do the following: • Access the metadata for types within an assembly. This includes con- structs such as the full type name, member names, and any attributes decorating the construct. • Dynamically invoke a type’s member at runtime using the metadata, rather than a compile-time-defined binding. Reflection is the process of examining the metadata within an assem- bly. Traditionally, when code compiles down to a machine language, all the metadata (such as type and method names) about the code is dis- carded. In contrast, when C# compiles into the CIL, it maintains most of the metadata about the code. Furthermore, using reflection, it is possible to enumerate through all the types within an assembly and search for those that match certain criteria. You access a type’s metadata through instances of System.Type, and this object includes methods for enumerating the type instance’s members. Furthermore, it is possible to invoke those members on particular objects that are of the examined type. The facility for reflection enables a host of new paradigms that other- wise are unavailable. For example, reflection enables you to enumerate over all the types within an assembly, along with their members, and in the process create stubs for documentation of the assembly API. You can then combine the metadata retrieved from reflection with the XML document created from XML comments (using the/doc switch) to create the API doc- umentation. Similarly, programmers use reflection metadata to generate code for persisting (serializing) business objects into a database. It could also be used in a list control that displays a collection of objects. Given the collection, a list control could use reflection to iterate over all the proper- ties of an object in the collection, defining a column within the list for each From the Library of Wow! eBook
  15. 653 R eflection property. Furthermore, by invoking each property on each object, the list control could populate each row and column with the data contained in the object, even though the data type of the object is unknown at compile time. XmlSerializer, ValueType, and DataBinder are a few of the classes in the framework that use reflection for portions of their implementation as well. Accessing Metadata Using System.Type The key to reading a type’s metadata is to obtain an instance of System.Type that represents the target type instance. System.Type pro- vides all the methods for retrieving the information about a type. You can use it to answer questions such as the following. • What is the type’s name (Type.Name)? • Is the type public (Type.IsPublic)? • What is the type’s base type (Type.BaseType)? • Does the type support any interfaces (Type.GetInterfaces())? • Which assembly is the type defined in (Type.Assembly)? • What are a type’s properties, methods, fields, and so on (Type.Get- Properties(), Type.GetMethods(), Type.GetFields(), and so on)? • What attributes decorate a type (Type.GetCustomAttributes())? There are more such members, but in summary, they all provide infor - mation about a particular type. The key is to obtain a reference to a type’s Type object, and the two primary ways to do this are through object. GetType() and typeof(). Note that the GetMethods() call does not return extension methods. They are available only as static members on the implementing type. GetType() object includes a GetType() member, and therefore, all types include this function. You call GetType() to retrieve an instance of System.Type corre- sponding to the original object. Listing 17.1 demonstrates this, using a Type instance from DateTime. Output 17.1 shows the results. From the Library of Wow! eBook
  16. 654 C hapter 17: Reflection, Attributes, and Dynamic Programming Listing 17.1: Using Type.GetProperties() to Obtain an Object’s Public Properties DateTime dateTime = new DateTime(); Type type = dateTime.GetType(); foreach ( System.Reflection.PropertyInfo property in type.GetProperties()) { Console.WriteLine(property.Name); } OUTPUT 17.1: Date Day DayOfWeek DayOfYear Hour Kind Millisecond Minute Month Now UtcNow Second Ticks TimeOfDay Today Year After calling GetType(), you iterate over each System.Reflection. PropertyInfo instance returned from Type.GetProperties() and display the property names. The key to calling GetType() is that you must have an object instance. However, sometimes no such instance is available. Static classes, for example, cannot be instantiated, so there is no way to call GetType(). typeof() Another way to retrieve a Type object is with the typeof expression. typeof binds at compile time to a particular Type instance, and it takes a type directly as a parameter. Listing 17.2 demonstrates the use of typeof with Enum.Parse(). From the Library of Wow! eBook
  17. 655 R eflection Listing 17.2: Using typeof() to Create a System.Type Instance using System.Diagnostics; // ... ThreadPriorityLevel priority; priority = (ThreadPriorityLevel)Enum.Parse( typeof(ThreadPriorityLevel), "Idle"); // ... Enum.Parse() takes a Type object identifying an enum and then converts a string to the specific enum value. In this case, it converts "Idle" to System.Diagnostics.ThreadPriorityLevel.Idle. Member Invocation The possibilities with reflection don’t stop with retrieving the metadata. The next step is to take the metadata and dynamically invoke the members it references. Consider the possibility of defining a class to represent an application’s command line. The difficulty with a CommandLineInfo class such as this has to do with populating the class with the actual command- line data that started the application. However, using reflection, you can map the command-line options to property names and then dynamically set the properties at runtime. Listing 17.3 demonstrates this example. Listing 17.3: Dynamically Invoking a Member using System; using System.Diagnostics; public partial class Program { public static void Main(string[] args) { string errorMessage; CommandLineInfo commandLine = new CommandLineInfo(); if (!CommandLineHandler.TryParse( args, commandLine, out errorMessage)) { Console.WriteLine(errorMessage); DisplayHelp(); } if (commandLine.Help) { From the Library of Wow! eBook
  18. 656 C hapter 17: Reflection, Attributes, and Dynamic Programming DisplayHelp(); } else { if (commandLine.Priority != ProcessPriorityClass.Normal) { // Change thread priority } } // ... } private static void DisplayHelp() { // Display the command-line help. } } using System; using System.Diagnostics; public partial class Program { private class CommandLineInfo { public bool Help { get; set; } public string Out { get; set; } public ProcessPriorityClass Priority { get { return _Priority; } set { _Priority = value; } } private ProcessPriorityClass _Priority = ProcessPriorityClass.Normal; } } using System; using System.Diagnostics; using System.Reflection; public class CommandLineHandler { From the Library of Wow! eBook
  19. 657 R eflection public static void Parse(string[] args, object commandLine) { string errorMessage; if (!TryParse(args, commandLine, out errorMessage)) { throw new ApplicationException(errorMessage); } } public static bool TryParse(string[] args, object commandLine, out string errorMessage) { bool success = false; errorMessage = null; foreach (string arg in args) { string option; if (arg[0] == '/' || arg[0] == '-') { string[] optionParts = arg.Split( new char[] { ':' }, 2); // Remove the slash|dash option = optionParts[0].Remove(0, 1); PropertyInfo property = commandLine.GetType().GetProperty(option, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public); if (property != null) { if (property.PropertyType == typeof(bool)) { // Last parameters for handling indexers property.SetValue( commandLine, true, null); success = true; } else if ( property.PropertyType == typeof(string)) { property.SetValue( commandLine, optionParts[1], null); success = true; } else if (property.PropertyType.IsEnum) { try { property.SetValue(commandLine, Enum.Parse( From the Library of Wow! eBook
  20. 658 C hapter 17: Reflection, Attributes, and Dynamic Programming typeof(ProcessPriorityClass), optionParts[1], true), null); success = true; } catch (ArgumentException ) { success = false; errorMessage = string.Format( "The option '{0}' is " + "invalid for '{1}'", optionParts[1], option); } } else { success = false; errorMessage = string.Format( "Data type '{0}' on {1} is not" + " supported.", property.PropertyType.ToString(), commandLine.GetType().ToString()); } } else { success = false; errorMessage = string.Format( "Option '{0}' is not supported.", option); } } } return success; } } Although Listing 17.3 is long, the code is relatively simple. Main() begins by instantiating a CommandLineInfo class. This type is defined spe- cifically to contain the command-line data for this program. Each property corresponds to a command-line option for the program where the com- mand line is as shown in Output 17.2. OUTPUT 17.2: Compress.exe /Out: /Help /Priority:RealTime|High|AboveNormal|Normal|BelowNormal|Idle From the Library of Wow! eBook
Đồng bộ tài khoản