Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Enumerated Types

An enumerated type is a type that defines a set of symbolic name and value pairs. For example, the

Color type shown here defines a set of symbols, with each symbol identifying a single color.

 

internal enum Color {

White, // Assigned a value of 0 Red, // Assigned a value of 1 Green, // Assigned a value of 2 Blue, // Assigned a value of 3 Orange // Assigned a value of 4

}

 

Of course, programmers can always write a program using 0 to represent white, 1 to represent red, and so on. However, programmers shouldn’t hard-code numbers into their code and should use an enumerated type instead, for at least two reasons:

■ Enumerated types make the program much easier to write, read, and maintain. With enumer- ated types, the symbolic name is used throughout the code, and the programmer doesn’t have to mentally map the meaning of each hard-coded value (for example, white is 0 or vice versa). Also, should a symbol’s numeric value change, the code can simply be recompiled without requiring any changes to the source code. In addition, documentation tools and other utilities, such as a debugger, can show meaningful symbolic names to the programmer.

 


■ Enumerated types are strongly typed. For example, the compiler will report an error if I at- tempt to pass Color.Orange as a value to a method requiring a Fruit enumerated type as a parameter.

In the Microsoft .NET Framework, enumerated types are more than just symbols that the compiler cares about. Enumerated types are treated as first-class citizens in the type system, which allows for very powerful operations that simply can’t be done with enumerated types in other environments (such as in unmanaged C++, for example).

Every enumerated type is derived directly from System.Enum, which is derived from System. ValueType, which in turn is derived from System.Object. So enumerated types are value types (described in Chapter 5, “Primitive, Reference, and Value Types”) and can be represented in unboxed and boxed forms. However, unlike other value types, an enumerated type can’t define any methods, properties, or events. However, you can use C#’s extension methods feature to simulate adding meth- ods to an enumerated type. See the “Adding Methods to Enumerated Types” section at the end of this chapter for an example of this.

When an enumerated type is compiled, the C# compiler turns each symbol into a constant field of the type. For example, the compiler treats the Color enumeration shown earlier as if you had written code similar to the following.

 

internal struct Color : System.Enum {

// Following are public constants defining Color's symbols and values public const Color White = (Color) 0;

public const Color Red = (Color) 1; public const Color Green = (Color) 2; public const Color Blue = (Color) 3; public const Color Orange = (Color) 4;

 

// Following is a public instance field containing a Color variable's value

// You cannot write code that references this instance field directly public Int32 value ;



}

 

The C# compiler won’t actually compile this code because it forbids you from defining a type derived from the special System.Enum type. However, this pseudo-type definition shows you what’s happening internally. Basically, an enumerated type is just a structure with a bunch of constant fields defined in it and one instance field. The constant fields are emitted to the assembly’s metadata and can be accessed via reflection. This means that you can get all of the symbols and their values associ- ated with an enumerated type at run time. It also means that you can convert a string symbol into

its equivalent numeric value. These operations are made available to you by the System.Enum base type, which offers several static and instance methods that can be performed on an instance of an enumerated type, saving you the trouble of having to use reflection. I’ll discuss some of these opera- tions next.


 

For example, the System.Enum type has a static method called GetUnderlyingType, and the

System.Type type has an instance method called GetEnumUnderlyingType.

 

public static Type GetUnderlyingType(Type enumType); // Defined in System.Enum public Type GetEnumUnderlyingType(); // Defined in System.Type

 

These methods return the core type used to hold an enumerated type’s value. Every enumerated type has an underlying type, which can be a byte, sbyte, short, ushort, int (the most common type and what C# chooses by default), uint, long, or ulong. Of course, these C# primitive types correspond to FCL types. However, to make the implementation of the compiler itself simpler, the C# compiler requires you to specify a primitive type name here; using an FCL type name (such as Int32) generates the following message: error CS1008: Type byte, sbyte, short, ushort, int, uint, long, or ulong expected. The following code shows how to declare an enumerated type with an underlying type of byte (System.Byte).

 

internal enum Color : byte { White,

Red, Green, Blue, Orange

}

 

With the Color enumerated type defined in this way, the following code shows what Get­ UnderlyingType will return.

 

// The following line displays "System.Byte". Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));

 

The C# compiler treats enumerated types as primitive types. As such, you can use many of the familiar operators (==, !=, <, >, <=, >=, +, ­, ^, &, |, ~, ++, and ­­) to manipulate enumerated type instances. All of these operators actually work on the value instance field inside each enumerated


type instance. Furthermore, the C# compiler allows you to explicitly cast instances of an enumerated type to a different enumerated type. You can also explicitly cast an enumerated type instance to a numeric type.

Given an instance of an enumerated type, it’s possible to map that value to one of several string representations by calling the ToString method inherited from System.Enum.

 

Color c = Color.Blue;

Console.WriteLine(c); // "Blue" (General format) Console.WriteLine(c.ToString()); // "Blue" (General format) Console.WriteLine(c.ToString("G")); // "Blue" (General format) Console.WriteLine(c.ToString("D")); // "3" (Decimal format) Console.WriteLine(c.ToString("X")); // "03" (Hex format)

       
   
 
 

 

In addition to the ToString method, the System.Enum type offers a static Format method that you can call to format an enumerated type’s value.

 

public static String Format(Type enumType, Object value, String format);

 

Generally, I prefer to call the ToString method because it requires less code and it’s easier to call. But using Format has one advantage over ToString: Format lets you pass a numeric value for the value parameter; you don’t have to have an instance of the enumerated type. For example, the fol- lowing code will display “Blue”.

 

// The following line displays "Blue". Console.WriteLine(Enum.Format(typeof(Color), (Byte)3, "G"));

       
   
 
 

 

It’s also possible to call System.Enum’s static GetValues method or System.Type’s instance GetEnumValues method to obtain an array that contains one element for each symbolic name in an enumerated type; each element contains the symbolic name’s numeric value.

 

public static Array GetValues(Type enumType); // Defined in System.Enum public Array GetEnumValues(); // Defined in System.Type


Using this method along with the ToString method, you can display all of an enumerated type’s symbolic and numeric values, like the following.

 

Color[] colors = (Color[]) Enum.GetValues(typeof(Color)); Console.WriteLine("Number of symbols defined: " + colors.Length); Console.WriteLine("Value\tSymbol\n­­­­­\t­­­­­­");

foreach (Color c in colors) {

// Display each symbol in Decimal and General format. Console.WriteLine("{0,5:D}\t{0:G}", c);

}

 

The previous code produces the following output.

 

Number of symbols defined: 5 Value Symbol

­­­­­ ­­­­­­

0 White

1 Red

2 Green

3 Blue

4 Orange

 

Personally, I don’t like the GetValues and GetEnumValues methods because they both return an

Array, which I then have to cast to the appropriate array type. So, I always define my own method.

 

public static TEnum[] GetEnumValues<TEnum>() where TEnum : struct { return (TEnum[])Enum.GetValues(typeof(TEnum));

}

 

With my generic GetEnumValues method, I can get better compile-time type-safety and simplify

the first line of code in the previous example to the following.

 

Color[] colors = GetEnumValues<Color>();

 

This discussion shows some of the cool operations that can be performed on enumerated types.

I suspect that the ToString method with the general format will be used quite frequently to show symbolic names in a program’s user interface elements (list boxes, combo boxes, and the like), as long as the strings don’t need to be localized (because enumerated types offer no support for localization). In addition to the GetValues method, the System.Enum type and the System.Type type also offer the following methods that return an enumerated type’s symbols.

 

// Returns a String representation for the numeric value

public static String GetName(Type enumType, Object value); // Defined in System.Enum public String GetEnumName(Object value); // Defined in System.Type

 

 

// Returns an array of Strings: one per symbol defined in the enum

public static String[] GetNames(Type enumType); // Defined in System.Enum public String[] GetEnumNames(); // Defined in System.Type


I’ve discussed a lot of methods that you can use to look up an enumerated type’s symbol. But you also need a method that can look up a symbol’s equivalent value, an operation that could be used to convert a symbol that a user enters into a text box, for example. Converting a symbol to an instance of an enumerated type is easily accomplished by using one of Enum’s static Parse and TryParse methods.

 

public static Object Parse(Type enumType, String value);

public static Object Parse(Type enumType, String value, Boolean ignoreCase);

public static Boolean TryParse<TEnum>(String value, out TEnum result) where TEnum: struct; public static Boolean TryParse<TEnum>(String value, Boolean ignoreCase, out TEnum result)

where TEnum : struct;

 

Here’s some code demonstrating how to use this method.

 

// Because Orange is defined as 4, 'c' is initialized to 4. Color c = (Color) Enum.Parse(typeof(Color), "orange", true);

 

// Because Brown isn't defined, an ArgumentException is thrown. c = (Color) Enum.Parse(typeof(Color), "Brown", false);

 

// Creates an instance of the Color enum with a value of 1 Enum.TryParse<Color>("1", false, out c);

 

// Creates an instance of the Color enum with a value of 23 Enum.TryParse<Color>("23", false, out c);

 

Finally, using the following Enum’s static IsDefined method and Type’s IsEnumDefined method:

 

public static Boolean IsDefined(Type enumType, Object value); // Defined in System.Enum public Boolean IsEnumDefined(Object value); // Defined in System.Type

 

you can determine whether a numeric value is legal for an enumerated type.

 

// Displays "True" because Color defines Red as 1 Console.WriteLine(Enum.IsDefined(typeof(Color), (Byte)1));

 

// Displays "True" because Color defines White as 0 Console.WriteLine(Enum.IsDefined(typeof(Color), "White"));

 

// Displays "False" because a case­sensitive check is performed Console.WriteLine(Enum.IsDefined(typeof(Color), "white"));

 

// Displays "False" because Color doesn't have a symbol of value 10 Console.WriteLine(Enum.IsDefined(typeof(Color), (Byte)10));

 

The IsDefined method is frequently used for parameter validation. Here’s an example.

 

public void SetColor(Color c) {

if (!Enum.IsDefined(typeof(Color), c)) {

throw(new ArgumentOutOfRangeException("c", c, "Invalid Color value."));

}

// Set color to White, Red, Green, Blue, or Orange

...

}


The parameter validation is useful because someone could call SetColor like the following.

 

SetColor((Color) 547);

 

Because no symbol has a corresponding value of 547, the SetColor method will throw an

ArgumentOutOfRangeException exception, indicating which parameter is invalid and why.

       
   
 
 

 

Finally, the System.Enum type offers a set of static ToObject methods that convert an instance of a Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, or UInt64 to an instance of an enumer- ated type.

Enumerated types are always used in conjunction with some other type. Typically, they’re used for the type’s method parameters or return type, properties, and fields. A common question that arises is whether to define the enumerated type nested within the type that requires it or to define the enu- merated type at the same level as the type that requires it. If you examine the FCL, you’ll see that an enumerated type is usually defined at the same level as the class that requires it. The reason is simply to make the developer’s life a little easier by reducing the amount of typing required. So you should define your enumerated type at the same level unless you’re concerned about name conflicts.

 

 


Date: 2016-03-03; view: 705


<== previous page | next page ==>
Nbsp;   Secure Strings | Nbsp;   Bit Flags
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.011 sec.)