Early in my C# coding life I actually liked enums. They're neater than C enums (which are glorified int constants) and they're type-safe. But lately I've grown to hate them. To me, they are a leading cause of verbosity in C# code and those who use them tend to produce procedural code. I'll show why I feel this is so, and I'll discuss an approach you can use to weed them out.
I remember a project I worked on earlier this year in a previous employer. Some parts of the code made use of Model-View-Presenter and some don't. I guess such a thing is kinda expected when development teams change throughout the different phases of the project. But there's this particular thing in the WinForms code that stood out like a sore toe. The UI had a grid control and an array of buttons in the lower portion of the form. In this case, each button had a particular enum assigned to its Tag property. All the buttons had just one event handler, and eventually the call to the event handler gets to a massive switch-case block which calls the appropriate view/form method for the button. Massive pain!
Enums are simply labels (or, in the example I cited above, tags). You can assign them anywhere to allow you to distinguish between different cases that are represented by the enum. For example, if you want to distinguish between days, you can use a DayOfWeek enum, which comes built into .NET anyway. If you want to distinguish between different data types you can have some enum like MyDataType.String, MyDataType.Integer, MyDataType.Decimal, and so on. But then...HOW do you actually plan to distinguish between them later in your code? More often than not, it will involve a massive if or switch block. I mean, pull out any .NET code you can find that uses enums...it's very likely that it will have that conditional block to distinguish between the different values for that enum. It is equally likely that duplication or similar code (cut-n-paste code?) can be found across the various case handlers in that particular if or switch block.
The object-oriented way of dealing with an enum is to...throw it out!
I am not kidding. Instead, replace them with polymorphic behavior. The example I cited above with all form buttons having a common event handler but differing enum values assigned to each button's Tag property can be refactored into using the Command pattern. The WinForms default event handling mechanism is in fact very similar already to the Command pattern so the simpler solution might be to simply switch back to the WinForms way of doing things (having a delegate to handle each Button's Click event). On the other hand, my example above about handling different data types can be dealt with using something like the Strategy pattern. So you can have separate classes for handling string, integer and decimal operations having the same interface or superclass. So instead of doing something like:
HideAllFields();
switch (dataType) {
case MyDataType.String:
StringTextField.Text = someValue;
StringTextField.Visible = true;
break;
case MyDataType.Integer:
IntTextField.Text = someValue.ToString();
IntTextField.Visible = true;
break;
case MyDataType.Decimal:
DecTextField.Text = someValue.ToString();
DecTextField.Visible = true;
break;
}
You can do something like:
HideAllFields();
myData.EnableControlAndSetValue(someValue);
and have different classes representing string, integer and decimal deriving from the same class or interface with a virtual method named EnableControlAndSetValue. In this case the class for handling strings can look like:
public class MyStringType : MyBaseType {
public MyStringType(TextBox textFieldControl) : base(textFieldControl) {
}
public override void EnableControlAndSetValue(object someValue) {
myPrivateTextBox.Text = someValue.ToString();
myPrivateTextBox.Visible = true;
}
}
You might be thinking: "Hey, that means more code to type!" In a way, yes, that's true for this example. But imagine if you have twenty possible data types, and suddenly the requirement is changed and you have to support thirty possible data types. With the enum approach, you'll have to change the enum to add ten new enum entries, and you'll have to extend your switch statement to support thirty cases. In contrast, with the Strategy pattern approach here will only require you to create ten new short classes to correspond to those new data types, each differing only in their overridden EnableControlAndSetValue method. If all your classes do the same routine (setting the textbox Text property and making it Visible) then you can even move the EnableControlAndSetValue method to the base class (e.g. MyBaseType) and you won't have to override it at all in each class.
You might think that this example is contrived and puts enums in the worst light possible and that there are pros and cons to the enum approach vs. the polymorphic approach. Yes, there definitely are trade-offs, but in my case I prefer smaller, more focused classes than an unwieldy series of if/switch statements. So these days I have shunned enums. I only work with them when I am forced to do so (i.e. legacy code I need to maintain at work -- and I certainly feel the itch to get it refactored!).
UPDATE: The master showed me these links: Replace Conditional Logic with Strategy and Replace Conditional Dispatcher with Command. Thanks master!