|
|
|
Have you ever found the existing datatypes a bit wanting, in terms of representing what you want? We can use integers and doubles to represent any number, and strings to represent any character, but what if we want a datatype that represents something more specific? For example, gender, or a month, or a day of the week? Let's go with the day of the week, first. One thing we could do would be to simply store this information as an integer, and pretend that 0 is "Monday", 1 is "Tuesday", and so on. But that would be kind of lame. For one thing, there's no guarantee that we're checking for the same values consistently, across our entire program. How do we know for sure that we didn't code "Monday" to be 0 in one place, and "Sunday" to be 0 someplace else? For another thing, how do we handle values like 31442, which is a perfectly valid integer, but would be way out of bounds for representing a day of the week? And a string variable would have the same problems, only we'd be worrying about case-sensitivity on top of all that. C# solves this problem by allowing us to define our own datatypes for variables with a finite set of values, through the use of enums. Behold, a datatype for representing the days of the week! We'll declare it above "Class1", so that's it's only in the namespace... enum WeekDay: int { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } Remember when I said "a finite set of values"? Now that you've seen an example of an enum, you might understand why. When we create an enum, we define every possible value that the datatype can have. Only the values that we specify will be acceptable values for the enum to store. That's why things like Gender, Month, and WeekDay are great examples of enum usage: because they have a limited number of possible values. Year, on the other hand, would be a really stupid place for an enum, because the year could theoretically be any number imaginable. And although DayOfMonth is never going to be greater than 31, which gives it a limited number of values, it's also a poor choice for an enum, because it can be represented just fine by an ordinary integer. Use enums when you want to use a limited number of words as your possible values. So, now that we've defined an enum, how do we use it? We can create variables of WeekDay and assign values to them like so: WeekDay day = WeekDay.Monday; // Stores value of Monday in day That's how any value in an enum is referenced; by qualifying it with the name of the enum (so you might want to avoid lengthy enum names). We can evaluate an enum variable using the same technique: // Do something if the value of day is Tuesday if( day == WeekDay.Tuesday ) { } The :int that we place after the enum name tells C# that the enum is using integers "under the hood" to represent our actual values. In fact, we can even specify which integer value to use for each value, by doing this: enum Gender: int { Male = 1, Female = 2, } However, this is rarely useful (unless you're doing flags, which is covered in the next lesson), because one of the main benefits of enums is that you don't need to know the "under the hood value". If the numeric value actually matters, it's probably worth asking yourself why you're using an enum at all. There are a few cases where it is useful to associate a specific number with a specific enum value, and in these cases, you can use (int) casting to extract the enum's underlying value. In any other case, when you don't manually assign numbers to your various enum values, C# will simply pick a unique number for each one, and you don't have to worry about it at all. So relax, and stop worrying about whether June is a 5 or a 6 when your program runs! It is sometimes useful to convert between enum values and strings values (and vice versa), especially when user input is involved. Going from an enum value to a string is no different than anything else; you'd simply write something like "day.ToString()" to get a string that spells out your enum's value. Going from a string to an enum, however, requires the use of the special Enum class... WeekDay day = (WeekDay)Enum.Parse(typeof(WeekDay), "Friday", true); This isn't really as scary as it looks; when you actually look at Enum.Parse(), you'll see that there's only three parameters. The first one is where you enter the enum type that you're trying to parse (WeekDay, Gender, Month, etc), which should be the same as the type that you cast it to. The second parameter is the actual string that you're parsing. The third parameter is optional. It's a boolean variable that can be set to "true", if you don't want the parsing to be case-sensitive (and you rarely want case-sensitivity here... it's stupid to have two enum values that only differ by case, anyway). Of course, if you try to parse "5@ ^ 62#$$$GG" as a WeekDay, you're going to get a thrown exception, so it can be useful to check if a string is a valid enum before parsing it: if( Enum.IsDefined(typeof(WeekDay), "Friday") ) { } Pass in the string that you're about to parse, and if the test passes, you know you're dealing with a legitimate value. When dealing with ComboBoxes in windows forms, it is often useful to populate the ComboBox with all the possible values of an enum. The following code populates a string array with all the possible values for WeekDay (which can then be used to populate the ComboBox)... string[] weekDays = Enum.GetNames(typeof(WeekDay)); We can also use Enum.GetValues() to populate an int array with all the possible underlying int values in a given enum. The code sample below summarizes all of this, but before we continue, a word of caution: enums should not be used as a substitute for polymorphism. What do I mean by this? Well, if you find that your program is becoming peppered with "if" statements that do different pieces of logic depending on the value of an enum, you should consider scrapping the enum altogether and writing each of the enum's values as inheriting classes of a common base class. This will allow you to put any specific logic right in a self-contained class, instead of spreading it throughout the program. As always, of course, this is an architectural decision that will play out a little differently every time, depending on what you need and what is pragmatic. using System; namespace B_Enums { // We can assign numeric values to each // possible value (but we don't have to). enum Gender: int { Male = 1, Female = 2, } // In fact, assigning numeric values is // rarely useful, since we should be using // the actual names anyway. enum Month: int { January, February, March, April, May, June, July, August, September, October, November, December, } class Class1 { static readonly string ln = Environment.NewLine; [STAThread] static void Main(string[] args) { // Assigning enum values Gender gen = Gender.Male; // Evaluating enum values if( gen == Gender.Male ) Console.WriteLine("One x chromosome"); else Console.WriteLine("Two x chromosomes"); // Converting enum values to string Month month = Month.July; Console.WriteLine(month.ToString()); // Parsing enum values from a string // The "true" tells C# to ignore upper/lower case, // but feel free to omit it if you don't want that. month = (Month)Enum.Parse(typeof(Month), "March", true); Console.WriteLine(month.ToString()); // Getting all possible names and values in a given enum string[] monthNames = Enum.GetNames(typeof(Month)); int[] monthValues = (int[])Enum.GetValues(typeof(Month)); for(int i = 0; i < monthNames.Length; ++i) { Console.WriteLine("{0} = {1}", monthNames[i], monthValues[i]); } // Test if a given name exists in a given enum if( Enum.IsDefined(typeof(Month), "December") ) Console.WriteLine("December is defined in enum Month."); else Console.WriteLine("December is not defined in enum Month."); if( Enum.IsDefined(typeof(Month), "Gualray") ) Console.WriteLine("Gualray is defined in enum Month."); else Console.WriteLine("Gualray is not defined in enum Month."); PromptForExit(); } static void PromptForExit() { Console.Write(ln + "Program complete! Hit enter to exit..."); Console.ReadLine(); } } } |
|