One of the most overlooked tools in modern programming languages for producing elegant code is the enumeration.
The enumeration is such a simple concept, yet it yields such a high return in both readability and usability of your code.
The enum is able to eliminate the need for XML style comments for a parameter to a method, and prevent the passing of null in one fell swoop.
- Enumerations make the set of choices for a parameter finite and self-describing.
- Enumerations which are parameters can never be set to null.
Explicit documentation becomes self-documentation
Let?s look at an example, so I can show you what I am talking about:
/// <summary> /// Causes pacman to eat the specified fruit. /// </summary> /// <param name="fruit">A fruit which must either be an apple, banana, orange, or /// cherries.</param> public void Eat(string fruit) { switch(fruit) { case "apple": Console.WriteLine("Ate an apple"); break; case "banana": Console.WriteLine("I love bananas"); break; case "orange": Console.WriteLine("A color and a fruit"); break; case "cherries": Console.WriteLine("This one is plural"); break; default: break; } }
You can see here that we need some comment to tell the user of the method what can be passed into the fruit parameter.
We have to handle the default case, in case someone is able to pass in something we didn?t expect.
A simple typo in calling the method or in our method, could result in a difficult to find bug.
Someone can call this method with null like so:
// passing in null is allowed, strings can be null, objects can always be null. Eat(null);
Now take a look at the example that uses an enumeration for fruits.
public void Eat(Fruits fruit) { switch (fruit) { case Fruits.Apple: Console.WriteLine("Ate an apple"); break; case Fruits.Banana: Console.WriteLine("I love bananas"); break; case Fruits.Orange: Console.WriteLine("A color and a fruit"); break; case Fruits.Cherries: Console.WriteLine("This one is plural"); break; } }
Do we really need the XML comment anymore?? When you try to call Eat() you will immediately see that it requires a Fruit and when you type ?Fruit.? you will get:
Pretty self-explanatory.
Consider what happens when I try to pass null into the method.
You cannot do it.? No matter how hard you try.
Side note: before the comments start coming in.? The example above is using a switch statement so that it can be simple for demonstration.? In real code you would want to do something smarter here like use a Dictionary<Fruits, string> and replace the switch.
The power of limiting choices
It is a funny kind of a seeming contradiction that limiting the amount of choices increases the effectiveness of something.
The real power of enumerations to make your code more elegant lies in this contradiction.? By constraining the choices of what can be passed into a method, it becomes possible to list those choices out.? Which allows intellisense to display the choices for you.? You are forcing the IDE to document your code automatically.
Options that are self-discoverable are always going to be easier to use and understand, because they don?t require the mind to parse a human sentence and translate it into code.
Limiting choices protects your code in many other ways.? You prevent typos on either end because the compiler will now tell you if you have typed one of the choices wrong.
Testing becomes simpler, because the choices are finite.? Edge conditions and testing invalid input don?t apply to methods that take enumerations.? All input possibilities are known.
When to use enumerations
It is more often than you would think.? Let me show you an example where many people might not notice they could use an enum:
public void StartGame(int numberOfPlayers);
Many programmers would look at this code and not even think about using an enumeration here.? The number of players feels like an integer.
Every time you are writing a method and declaring the parameters, you should be asking yourself ?How can I constrain the choices of what can be passed in??
In this case, it is very simple.? How many players does the game allow?? There might even be a constant MAX_PLAYERS = 4.
We can very easily convert this code to use a simple enum and constrain the choices.
public void StartGame(PlayerCountSelection numberOfPlayers);
This small change may not seem like much, but it is going to immediately eliminate the need for writing unit tests to check for invalid player amounts being passed in, and it is going to prevent another programmer using this method from having to look in the documentation to find out the valid ranges of the int.
It is the culmination of limiting choices throughout your code that produces a net effect of reducing the complexity of the system.? It is the same with most of the best practices I suggest on this blog.
So when should you use enumerations?? Any time you can.? Any time you have the opportunity to constrain input choices, do it.
Combine them with maps and function pointers for the ultimate combo!
With the switch implementation of the Eat method, I would still expect a comment. Not on the eat method, but on the fruit enumeration. When someone adds a fruit to the enumeration, he has to know that the eat method somewhere else in the code has to be changed as well. That way of coding looks more like C than C#. Instead of the comment approach, I would suggest a more OOP approach:
public class Fruit
{
public static readonly Fruit Apple = new Fruit(“Ate an apple”);
public static readonly Fruit Banana = new Fruit(“I love bananas”);
public static readonly Fruit Orange = new Fruit(“A color and a fruit”);
public static readonly Fruit Cherries = new Fruit(“This one is plural”);
private string eatDisplay;
private Fruit(string eatDisplay)
{
this.eatDisplay = eatDisplay;
}
public void Eat()
{
Console.WriteLine(eatDisplay);
}
}
With this approach, you can just add a new Fruit and than you’re done. No other classes need to be changed. This approach is a bit similar to the java enumeration. I have seen several .Net implementations of a enumeration base class to make it easier to create and use enumerations like this. I don’t understand why Microsoft treats enums as second based citizens in the .Net framework. Enumerations are very powerful.
@Paco
I agree with you almost 100%. When I looked at your code, I immediately thought you just recreated the Java enumeration.
Even a comments doesn’t really solve the problem of having to remember to add code when you add other items to the enumeration, because you can’t possibly document all the places the enum could be used. The best best here is to do a find references IMO.
I would like to see C# get a stronger enumeration type also.
Still, in any case using the enumeration is better than a string, that is what I am trying to show with that example.
One gotcha of C# Enums is that the compiler doesn’t enforce legal values.
For example, I could write the following statement:
Eat((Fruits)-12);
You always need a “default” path in your switch statements to handle cases where you get a value that hasn’t been defined.
A wrote up a post describing this sad fact a while ago:
http://underground.infovark.com/2008/07/28/enums-are-ints-that-aint/
Needless to say, I’d like to see a stronger Enum implementation in C# as well.
@Dean Thrasher
Good point. That is pretty disappointing. Of course if you never ever rely on the integer value of an Enum (which is probably a good practice), then you will never have that problem.
There should be a rule “if you are using enums, you are doing something wrong”. Enums promote wrong ways of doing things. This is correct way of doing things, compared to the wrong way presented in the article.
public class fruit{
public virtual Eat();
}
Public class Apple:Fruit{
Public Overrides Eat(){
Console.WriteLine(“Ate an apple”);
}
}
Public void Eat(Fruit fruit){
fruit.Eat();
}
As you can see, there is no need for switch, no need to do anything else, when introducing new fruit in the system. Enumerators will force you to change every switch statement you have in the code, and it actually promotes switches, which are wrong, wrong, wrong…..
P.S. code might not be correct (not compile), because i wrote it in word
P.P.S. Here is my comment, you know who!
@John Sonmez
A default case is still a good idea in case you add another enum value at a later date and forget to update your switch.
something like
default:
throw new NotImplementedException(“Fruit Not Recognised”);
break;
Another problem with enums is that they are essentially compile-time constants. That means if the enum is sourced from one assembly and potentially used in another you have a potential for errors. If the assembly the enum is defined modifies the enum (or god forbit rearranges it) the using assembly will not see those changes and will still pass what they consider the values to be.
Enums are also dangerous in web service contracts as they can cause parsing errors if the web service sends a new value in the enum that the client wasn’t expecting because it’s an older client.
I’m not saying enums are bad, but they should be limited to things that don’t change often or cross system boundaries. Even then, they have their quirks.
The default case as mentioned above is always a good idea because you can’t always guarantee your caller didn’t cast an int (say from a db table) to the enum and result in invaid behavior.
I agree with most of the problems you guys are describing about enums, but the point I am really trying to make with this post is that an enum is usually much better than just an int or a string being passed into a method.
What is your opinion on the pattern called :
-Replace Conditional with Polymorphism which basically means,
If you have a conditional that chooses different behavior depending on the type of an object.
Move each leg of the conditional to an overriding method in a subclass. Make the original method abstract.
Do you think its applicable here.
@Adil Ahmad
Sure, if we were doing this for a real application, we might have IFruit, and have an Apple, Orange etc class which all implemented a method Eat which had the different behavior.
Of course, we should not automatically jump to that pattern to solve this problem, because the solution maybe much simpler and be easily solved with a dictionary that maps the type of fruit to the message.
The point of this example was not to solve the problem of making the code as clean as possible, but to point out the use of an enum to improve that code as is. Perhaps I should have used a different example though.
How about this???
public enum Fruit{
APPLE{
public void print(){System.out.println(“i am apple”);}
},
ORANGE{
public void print(){System.out.println(“i am orange”);}
},MANGO{
public void print(){System.out.println(“i am mango”);}};
public abstract void print();
}
your eat() can now become
public void eat(Fruit fruit)
{
fruit.eat();// no dirty switch case
}
This is in java.I hope kind of enum is possible in C#.
@Abinesh
Typo in eat method
public void eat(Fruit fruit){
fruit.print();// no dirty switch case
}
First off, enumerations don’t constrain your set of choices e.g. if your Fruits enum “inherits from” the default of int you can pass this in to your method no problem: (Fruits)123456. Similarly Enum.Parse() will also accept “123456” as a valid value for Fruits. Enumerations simply allow you to give some of the values of, in this case, int a special meaning. It depends on how the values are getting into your system as to whether you’d ever need to worry about these unspecified values but it’s something you should be aware of.
Secondly, I think the best solution to you first example is what Abinesh was getting at i.e. a more OO solution where you have an abstract Fruit class with an abstract Eat() method with Apple, Banana etc classes inheriting from it and implementing the Eat() method.
Ha, just realised there was a previous page of comments and that stuff had already been covered…doh!
I like enums as replacements for magic numbers, per Raymond Chen at the Old New Thing. Using ints, bools, and other basic types as function parameters is bad since you can’t tell from the source code line what the values are SEMANTICALLY. Enumeration values provide a way to document what was meant, not just pass a value.
I also like how the switch-snippet in Visual Studio will automatically fill in all enumeration fields when you use it; e.g. switch-[TAB]-[TAB] fruits [TAB] [ENTER]
very nice and helpful