Addison Wesley Essential C Sharp_4 pot

98 214 0
Addison Wesley Essential C Sharp_4 pot

Đ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

ptg Boxing 345 The result is that no copy back from the heap to the stack occurs. Instead, the modified heap data is ready for garbage collection while the data in angle remains unmodified. In the last case, the cast to IAngle occurs with the data on the heap already, so no copy occurs. MoveTo() updates the _Hours value and the code behaves as desired. ADVANCED TOPIC Unboxing Avoided As discussed earlier, the unboxing instruction does not include the copy back to the stack. Although some languages support the ability to access value types on the heap directly, this is possible in C# only when the value type is accessed as a field on a reference type. Since interfaces are reference types, unboxing and copying can be avoided, when accessing the boxed value via its interface. When you call an interface method on a value type, the instance must be a variable because the method might mutate the value. Since unboxing produces a managed address, the runtime has a storage location and hence a variable. As a result, the runtime simply passes that managed address on an interface and no unboxing operation is necessary. Listing 8.7 added an interface implementation to the Angle struct. List- ing 8.8 uses the interface to avoid unboxing. Listing 8.8: Avoiding Unboxing and Copying int number; object thing; number = 42; // Boxing thing = number; string text = ((IFormattable)thing).ToString( "X", null); Console.WriteLine(text); Interfaces are reference types anyway, so calling an interface member does not even require unboxing. Furthermore, calling a struct’s ToString() method (that overrides object’s ToString() method) does not // No unbox instruction. From the Library of Wow! eBook ptg Chapter 8: Value Types346 require an unbox. When compiling, it is clear that a struct’s overriding ToString() method will always be called because all value types are sealed. The result is that the C# compiler can instruct a direct call to the method without unboxing. Enums Compare the two code snippets shown in Listing 8.9. Listing 8.9: Comparing an Integer Switch to an Enum Switch int connectionState; // switch (connectionState) { case 0: // break; case 1: // break; case 2: // break; case 3: // break; } ConnectionState connectionState; // switch (connectionState) { case ConnectionState.Connected: // break; case ConnectionState.Connecting: // break; case ConnectionState.Disconnected: // break; case ConnectionState.Disconnecting: // break; } From the Library of Wow! eBook ptg Enums 347 Obviously, the difference in terms of readability is tremendous because in the second snippet, the cases are self-documenting to some degree. How- ever, the performance at runtime is identical. To achieve this, the second snippet uses enum values in each case statement. An enum is a type that the developer can define. The key characteristic of an enum is that it identifies a compile-time-defined set of possible val- ues, each value referred to by name, making the code easier to read. You define an enum using a style similar to that for a class, as Listing 8.10 shows. Listing 8.10: Defining an Enum enum ConnectionState { Disconnected, Connecting, Connected, Disconnecting } You refer to an enum value by prefixing it with the enum name; to refer to the Connected value, for example, you use ConnectionState.Connected. You should not use the enum names within the enum value name, to avoid the redundancy of something such as ConnectionState.ConnectionStateCon- nected. By convention, the enum name itself should be singular, unless the enums are bit flags (discussed shortly). By default, the first enum value is 0 (technically, it is 0 implicitly con- verted to the underlying enum type), and each subsequent entry increases by one. However, you can assign explicit values to enums, as shown in Listing 8.11. NOTE An enum is helpful even for Boolean parameters. For example, a method call such as SetState(true) is less readable than SetState (DeviceState.On). From the Library of Wow! eBook ptg Chapter 8: Value Types348 Listing 8.11: Defining an Enum Type enum ConnectionState : short { Disconnected, Connecting = 10, Connected, Joined = Connected, Disconnecting } Disconnected has a default value of 0, Connecting has been explicitly assigned 10, and consequently, Connected will be assigned 11. Joined is assigned 11, the value referred to by Connected. (In this case, you do not need to prefix Connected with the enum name, since it appears within its scope.) Disconnecting is 12. An enum always has an underlying type, which may be int, uint, long, or ulong, but not char. In fact, the enum type’s performance is equivalent to that of the underlying type. By default, the underlying value type is int, but you can specify a different type using inheritance type syntax. Instead of int, for example, Listing 8.11 uses a short. For consistency, the syntax emulates that of inheritance, but this doesn’t actually make an inheritance relationship. The base class for all enums is System.Enum. Furthermore, these classes are sealed; you can’t derive from an existing enum type to add additional members. Successful conversion doesn’t work just for valid enum values. It is pos- sible to cast 42 into a ConnectionState, even though there is no corre- sponding ConnectionState enum value. If the value successfully converts to the underlying type, the conversion will be successful. The advantage to allowing casting, even without a corresponding enum value, is that enums can have new values added in later API releases, with- out breaking earlier versions. Additionally, the enum values provide names for the known values while still allowing unknown values to be assigned at runtime. The burden is that developers must code defensively for the possi- bility of unnamed values. It would be unwise, for example, to replace case ConnectionState.Disconnecting with default and expect that the only pos- sible value for the default case was ConnectionState.Disconnecting. Instead, you should handle the Disconnecting case explicitly and the Default case should report an error or behave innocuously. As indicated From the Library of Wow! eBook ptg Enums 349 before, however, conversion between the enum and the underlying type, and vice versa, involves an explicit cast, not an implicit conversion. For example, code cannot call ReportState(10) where the signature is void Report- State(ConnectionState state). (The only exception is passing 0 because there is an implicit conversion from 0 to any enum.) The compiler will per- form a type check and require an explicit cast if the type is not identical. Although you can add additional values to an enum in a later version of your code, you should do this with care. Inserting an enum value in the middle of an enum will bump the values of all later enums (adding Flooded or Locked before Connected will change the Connected value, for example). This will affect the versions of all code that is recompiled against the new version. However, any code compiled against the old version will continue to use the old values, making the intended values entirely differ- ent. Besides inserting an enum value at the end of the list, one way to avoid changing enum values is to assign values explicitly. Enums are slightly different from other value types because enums derive from System.Enum before deriving from System.ValueType. Type Compatibility between Enums C# also does not support a direct cast between arrays of two different enums. However, there is a way to coerce the conversion by casting first to an array and then to the second enum. The requirement is that both enums share the same underlying type, and the trick is to cast first to Sys- tem.Array, as shown at the end of Listing 8.12. Listing 8.12: Casting between Arrays of Enums enum ConnectionState1 { Disconnected, Connecting, Connected, Disconnecting } enum ConnectionState2 { Disconnected, Connecting, From the Library of Wow! eBook ptg Chapter 8: Value Types350 Connected, Disconnecting } class Program { static void Main() { ConnectionState1[] states = } } This exploits the fact that the CLR’s notion of assignment compatibility is more lenient than C#’s. (The same trick is possible for illegal conversions, such as int[] to uint[].) However, use this approach cautiously because there is no C# specification detailing that this should work across different CLR implementations. Converting between Enums and Strings One of the conveniences associated with enums is the fact that the ToString() method, which is called by methods such as System.Con- sole.WriteLine(), writes out the enum value identifier: System.Diagnostics.Trace.WriteLine(string.Format( "The Connection is currently {0}.", ConnectionState.Disconnecting)); The preceding code will write the text in Output 8.3 to the trace buffer. Conversion from a string to an enum is a little harder to find because it involves a static method on the System.Enum base class. Listing 8.13 pro- vides an example of how to do it without generics (see Chapter 11), and Output 8.4 shows the results. (ConnectionState1[])(Array)new ConnectionState2[42]; OUTPUT 8.3: The Connection is currently Disconnecting. From the Library of Wow! eBook ptg Enums 351 Listing 8.13: Converting a String to an Enum Using Enum.Parse() ThreadPriorityLevel priority = (ThreadPriorityLevel)Enum.Parse( typeof(ThreadPriorityLevel), "Idle"); Console.WriteLine(priority); The first parameter to Enum.Parse() is the type, which you specify using the keyword typeof(). This is a compile-time way of identifying the type, like a literal for the type value (see Chapter 17). Until .NET Framework 4, there was no TryParse() method, so code prior to then should include appropriate exception handling if there is a chance the string will not correspond to an enum value identifier. .NET Framework 4’s TryParse<T>() method uses generics, but the type parameters can be implied, resulting in the to-enum conversion example shown in Listing 8.14. Listing 8.14: Converting a String to an Enum Using Enum.TryParse<T>() System.Threading.ThreadPriorityLevel priority; if(Enum.TryParse("Idle", out priority)) { Console.WriteLine(priority); } This conversion offers the advantage that there is no need to use exception handling if the string doesn’t convert. Instead, code can check the Boolean result returned from the call to TryParse<T>(). Regardless of whether code uses the “Parse” or “TryParse” approach, the key caution about converting from a string to an enum is that such a cast is not localizable. Therefore, developers should use this type of cast only for messages that are not exposed to users (assuming localization is a requirement). Enums as Flags Many times, developers not only want enum values to be unique, but they also want to be able to combine them to represent a combinatorial value. OUTPUT 8.4: Idle From the Library of Wow! eBook ptg Chapter 8: Value Types352 For example, consider System.IO.FileAttributes. This enum, shown in Listing 8.15, indicates various attributes on a file: read-only, hidden, archive, and so on. The difference is that unlike the ConnectionState attri- bute, where each enum value was mutually exclusive, the FileAttributes enum values can and are intended for combination: A file can be both read-only and hidden. To support this, each enum value is a unique bit (or a value that represents a particular combination). Listing 8.15: Using Enums As Flags public enum FileAttributes { ReadOnly = 1<<0, // 000000000000001 Hidden = 1<<1, // 000000000000010 System = 1<<2, // 000000000000100 Directory = 1<<4, // 000000000010000 Archive = 1<<5, // 000000000100000 Device = 1<<6, // 000000001000000 Normal = 1<<7, // 000000010000000 Temporary = 1<<8, // 000000100000000 SparseFile = 1<<9, // 000001000000000 ReparsePoint = 1<<10, // 000010000000000 Compressed = 1<<11, // 000100000000000 Offline = 1<<12, // 001000000000000 NotContentIndexed = 1<<13, // 010000000000000 Encrypted = 1<<14, // 100000000000000 } Because enums support combined values, the guideline for the enum name of bit flags is plural. To join enum values you use a bitwise OR operator, as shown in Listing 8.16. Listing 8.16: Using Bitwise OR and AND with Flag Enums using System; using System.IO; public class Program { public static void Main() { // string fileName = @"enumtest.txt"; From the Library of Wow! eBook ptg Enums 353 System.IO.FileInfo file = new System.IO.FileInfo(fileName); file.Attributes = FileAttributes.Hidden | FileAttributes.ReadOnly; Console.WriteLine("{0} | {1} = {2}", FileAttributes.Hidden, FileAttributes.ReadOnly, (int)file.Attributes); if ( (file.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) { throw new Exception("File is not hidden."); } if (( file.Attributes & FileAttributes.ReadOnly) != FileAttributes.ReadOnly) { throw new Exception("File is not read-only."); } // } The results of Listing 8.16 appear in Output 8.5. Using the bitwise OR operator allows you to set the file to both read-only and hidden. In addition, you can check for specific settings using the bit- wise AND operator. Each value within the enum does not need to correspond to only one flag. It is perfectly reasonable to define additional flags that correspond to frequent combinations of values. Listing 8.17 shows an example. Listing 8.17: Defining Enum Values for Frequent Combinations enum DistributedChannel { None = 0, Transacted = 1, Queued = 2, OUTPUT 8.5: Hidden | ReadOnly = 3 From the Library of Wow! eBook ptg Chapter 8: Value Types354 Encrypted = 4, Persisted = 16, } Furthermore, flags such as None are appropriate if there is the possibility that none is a valid value. In contrast, avoid enum values corresponding to things such as Maximum as the last enum, because Maximum could be inter- preted as a valid enum value. To check whether a value is included within an enum use the System.Enum.IsDefined() method. ADVANCED TOPIC FlagsAttribute If you decide to use flag-type values, the enum should include FlagsAt- tribute. The attribute appears in square brackets (see Chapter 17), just prior to the enum declaration, as shown in Listing 8.18. Listing 8.18: Using FlagsAttribute // FileAttributes defined in System.IO. public enum FileAttributes { ReadOnly = 1<<0, // 000000000000001 Hidden = 1<<1, // 000000000000010 // } using System; using System.Diagnostics; using System.IO; class Program { public static void Main() { string fileName = @"enumtest.txt"; FileInfo file = new FileInfo(fileName); file.Open(FileMode.Create).Close(); FileAttributes startingAttributes = file.Attributes; FaultTolerant = Transacted | Queued | Persisted [Flags] // Decorating an enum with FlagsAttribute. From the Library of Wow! eBook [...]... source.Latitude + arc.LatitudeDifference Listing 9.8: Calling the – and + Binary Operators public class Program { public static void Main() { Coordinate coordinate1,coordinate2; coordinate1 = new Coordinate( new Longitude(48, 52), new Latitude(-2, -20)); Arc arc = new Arc(new Longitude(3), new Latitude(1)); coordinate2 = coordinate1 + arc; Console.WriteLine(coordinate2); coordinate2 = coordinate2 - arc; Console.WriteLine(coordinate2);... hash code calculation is readonly, the value can’t change However, implementations should cache the hash code if calculated values could change or if a cached value could offer a significant performance advantage Overriding Equals() Overriding Equals() without overriding GetHashCode() results in a warning such as that shown in Output 9.1 OUTPUT 9.1: warning CS0659: ’’ overrides Object.Equals(object... Longitude(-longitude.DecimalDegrees); } public static Longitude operator +(Longitude longitude) { return longitude; } } public struct Arc { // public static Arc operator -(Arc arc) { // Uses unary – operator defined on // Longitude and Latitude return new Arc( -arc.LongitudeDifference, -arc.LatitudeDifference); } public static Arc operator +(Arc arc) { return arc; } } Just as with numeric types, the + operator in this... Override GetHashCode public override int GetHashCode() { int hashCode = Longitude.GetHashCode(); hashCode ^= Latitude.GetHashCode(); // Xor (eXclusive OR) return hashCode; } } In this implementation, the first two checks are relatively obvious Checks 4–6 occur in an overload of Equals() that takes the Coordinate data type specifically This way, a comparison of two Coordinates will avoid Equals(object obj)... dedicated to functionality around longitude and latitude coordinates To compile the From the Library of Wow! eBook Referencing Other Assemblies 379 Coordinate, Longitude, and Latitude classes into their own library, you use the command line shown in Output 9.4 OUTPUT 9.4: >csc /target:library /out:Coordinates.dll Coordinate.cs IAngle.cs Latitude.cs Longitude.cs Arc.cs Microsoft (R) Visual C# 2010 Compiler... its GetType() check altogether Since GetHashCode() is not cached and is no more efficient than step 5, the GetHashCode() comparison is commented out Similarly, base.Equals() is not used since the base class is not overriding Equals() (The assertion checks that base is not of type object, however it does not check that the base class overrides Equals(), which is required to appropriately call base.Equals().)... Fortunately, C# provides for the definition of methods specifically to handle the converting of one type to another Furthermore, the method declaration allows for specifying whether the conversion is implicit or explicit ADVANCED TOPIC Cast Operator (()) Implementing the explicit and implicit conversion operators is not technically overloading the cast operator (()) However, this is effectively what takes place,... Resource Cleanup Overriding object Members 2 Garbage Collection Associating XML Comments with Programming Constructs Generating an XML Documentation File 1 5 Well-Formed Types XML Comments 4 Operator Overloading 3 Referencing Other Assemblies Defining Namespaces Overriding object Members Chapter 6 discussed how all types derive from object In addition, it reviewed each method available on object and discussed... references The Program class listing from Listing 9.8 uses the Coordinate class, and if you place this into a separate executable, you reference Coordinates.dll using the NET command line shown in Output 9.5 OUTPUT 9.5: csc.exe /R:Coordinates.dll Program.cs The Mono command line appears in Output 9.6 OUTPUT 9.6: msc.exe /R:Coordinates.dll Program.cs Encapsulation of Types Just as classes serve as an encapsulation... assembly references the assembly containing the class, all internal classes within the referenced assemblies will be inaccessible Just as private and protected provide levels of encapsulation to members within a class C# supports the use of access modifiers at the class level for control over the encapsulation of the classes within an assembly The access modifiers available are public and internal, . ConnectionState connectionState; // switch (connectionState) { case ConnectionState.Connected: // break; case ConnectionState.Connecting: // break; case ConnectionState.Disconnected: . Disconnected, Connecting = 10, Connected, Joined = Connected, Disconnecting } Disconnected has a default value of 0, Connecting has been explicitly assigned 10, and consequently, Connected. Connected, Disconnecting } enum ConnectionState2 { Disconnected, Connecting, From the Library of Wow! eBook ptg Chapter 8: Value Types350 Connected, Disconnecting } class Program { static void

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

Mục lục

  • Contents

  • Contents of C# 4.0 Topics

  • Figures

  • Tables

  • Foreword

  • Preface

  • Acknowledgments

  • About the Author

  • 1 Introducing C#

    • Hello, World

      • Compiling and Running the Application

      • C# Syntax Fundamentals

        • Type Definition

        • Main

        • Statements and Statement Delimiters

        • Whitespace

        • Working with Variables

          • Data Types

          • Declaring a Variable

          • Assigning a Variable

          • Using a Variable

          • Console Input and Output

            • Getting Input from the Console

            • Writing Output to the Console

            • Comments

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

Tài liệu liên quan