Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 98 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
98
Dung lượng
1,81 MB
Nội dung
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 . 6D 00 20 9C 11 C9 78 00 00 00 00 34 12 A6 00 00 00 00 00 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D4 4C C7 78 02 41 00 20 00 63 00 61 00 63 00 6F 00 70 00 68 00 6F 00 6E. 00 6D D4 4C C7 78 02 42 42 0x00A60289 0x00A64799 0x00A61 234 0x00A61 234 From the Library of Wow! eBook ptg Overriding object Members 36 3 class Program { static void Main() { ProductSerialNumber. if(!serialNumber1.Equals(serialNumber3) || serialNumber1 != serialNumber3) { throw new Exception( "serialNumber1 does NOT equal serialNumber3"); } From the Library of Wow! eBook ptg Chapter 9: Well-Formed Types364