0

Background: I have seem some argument for using enumeration classes instead of enum in C#, in particular, this section from a book available at MSDN. On the references there is this "Enums are Evil" article which in turn makes quite a good point on the matter.

One of the reasons quoted is particularly convincing:

Imagine the following phone call between Frank, a customer, and Darren the developer:

Frank: Hi Darren; I need a new value for the person status about his financial standings. We have currently Low, Medium and High, but we need ‘undefined’ as a new value.

Darren: I see.

Frank: When can you implement that?

Darren: Uups Frank. We just finished our sprint this week. The earliest we can plan this is for the sprint after the current one. That takes about 5 weeks.

Frank: What? It’s just another value not a whole new functionality!

Darren: Calm down Frank; we have these cool enum’s in place that saved us a lot of development time. To extend them I have to add it in the code and create a new build. We have to plan it for the next sprint.

Frank: Are you nuts? I don’t know what you talking about. I’m an ordinary user and just want to have another value on the user interface.

Darren: Sorry for that. I can’t do it earlier.

I've been through that already and it really feels like it is much more natural to allow the user to persist to the database the values he uses, like CardTypes as shown in the first MSDN link I provided:

public class CardType : Enumeration
{
    public static CardType Amex = new CardType(1, "Amex");
    public static CardType Visa = new CardType(2, "Visa");
    public static CardType MasterCard = new CardType(3, "MasterCard");

    public CardType(int id, string name)
    : base(id, name)
    {
    }
}

The problem: All of this is fine, and using these "enumeration classes" instead of enum has a lot of advantage. But now suppose the user comes and says that there is some specific rule for a specific item on the enumeration (for example, there is a very specific rule just for the Visa card).

With enums this is fairly easy. We would just perform a very natural check if (cardType == CardType.Visa). The point is: the values are all there on code to be checked.

But with enumerations the values are dynamical and added by the user. So the best I could consider would be to check for a string, querying the database's table containing the enumeration values for the specific item.

But I think this is too error prone - say the user alters the string for that enumeration, the query wouldn't work anymore. Further if the user made any typo when writing, it also wouldn't work.

Is there some better method?

In summary: if we use enumration classes instead of enum and if there is a business rule for a specific item that the user is adding dynamically, how can we verify and address that in a more reliable way than "checking a string"?

5 Answers 5

2

You need metadata

In order for a user to be able to add custom card types that may or may not have specific business rules or behavior, the user must be able to specify whether the behavior applies, and you need to be able to store it.

So you will need a metadata table, sort of like this:

CREATE TABLE CardTypes
(
    Code Char(4),
    Description VarChar(40),
    RequiresFooProcesser bit
)

You would then initialize the table with your default card types. In this example, the Visa card requires the FooProcessor, but the Amex doesn't.

INSERT CardTypes ( 'VISA','Standard VISA card', 1 )
INSERT CardTypes ( 'AMEX','Standard American Express', 0 )

Handling use cases dynamically

When a user enters a new card type, you expose a UI that lets them specify the card type code and tick a checkbox telling the application whether the magic FooProcessor is required. You then store the card type code, the description, and the flag that tells the system whether the FooProcessor is required. When you're done, the metadata for the user's new card type looks exactly like the metadata for the default card types.

When a user enters a new card, the UI asks the user to select the card type from a list populated from the metadata table. You then store the cardtype's Code with the card (as a foreign key) so that you know which metadata applies to the card that the user just entered.

When it is time to process the card, your code needs to check the flag.

So as a developer you would change this code:

//Old
if (card.Type == CardType.Visa)  //Check enum
{
    RunFooProcessor(card);
}

...to this code...

//New
var cardType = dbContext.CardTypes.Single( c => c.Code == card.Type );
if (cardType.RequiresFooProcessor)  //Check flag
{
    RunFooProcessor(card);
}

This elimlinates the need to check for specific enum values or specific card types; instead, you check for a flag that is assigned to the card type at run time.

The metadata can be useful for other purposes as well. For example, if card types are restricted by BIN, you can ask the user for a list of valid BINs and store them as additional metadata, and develop your system to validate card numbers correctly without actually knowing if something is a Visa, Amex, or whatever.

The point of this effort is to make the system agnostic with respect to card type and instead design it with generic concepts that can be associated with card types at run time.

4

Basically both of the articles you reference are wrong. You should use enums where you have an enum.

You should also have change control on your database. If you bypass your change control processes for your code you can add an enum value, recompile the code on your desktop machine, RDP to the server and copy the binaries over the top of the application and have that new value out in a couple of minutes.

There is a reason we don't do this and that reason applies equally to your database. You should be making a migration script, testing the app with the new value etc etc

Now if the enum is user editable you can obviously not hardcode it in the application, either as an enum or as static properties. It's just a string value.

But in this case you should provide the user with a UI that enables them to add/remove/edit those values in such a way that all the business rules are met. It shouldn't be a programming job at all.

2

With enums this is fairly easy. We would just perform a very natural check if (cardType == CardType.Visa). The point is: the values are all there on code to be checked.

If you are using a CardType class and there is some behavior that has to be different from one instance to another, then allow the CardType object to know it. So, if there are two ways of doing things (the regular way and the specific rule) you create two "strategies" and pass them to the constructors of CardType... then client code can access and use them. That way you would not have tons of switch statements, instead you just pull the "strategies".

In the orthodox way of doing this, the "strategies" would be instances of classes that inherit from a given class. However, you can have them be classes that implement a given interface... or just instances of a given delegate, heck, they can reference static methods.

See Strategy pattern.


But with enumerations the values are dynamical and added by the user.

If you are letting the user add new CardType entries on their own, you can have a field on the database that identifies the strategy you need.

That also means that you would use the value of that field to pull the right strategy in code. Meaning that you could have them in a “service locator”. At the simplest implementation, the “service locator” is a read-only static dictionary from where you can get the strategy you need by its identification in the database.

See Service locator pattern.


Thus, these values could be added by the user, but the strategies would still be under your control. Remember that the idea of these strategies is that each one has a unique behavior (not just data), meaning that they need custom code backing them up, which also means it makes no sense to let the user add them (unless we are talking of adding a script language or something like that).

To be more clear, you can let the user add CardType entries. Each CardType would have a CardStrategy, and these would remain under your control. The user could choose which CardStrategy to use for each CardType but not add new CardStrategy entries, because they require code backing them up.


But I think this is too error prone - say the user alters the string for that enumeration, the query wouldn't work anymore.

If the strategy is set on the database or the code independently from the text then this is not a problem.

I will remind you to not use the text shown to the user as primary key. So that the user can change the text without having to propagate the change to any other table that references it. Similarly, which strategy to use would be a separate field.

1
  • 1
    Another reason to not use the text shown to the user as primary key is localization. You don't want the primary key, or resulting program logic, to depend on what language the user has selected. (Plus the complication this adds when there are two users accessing the application using different languages.) Commented Jul 15, 2019 at 5:00
0

Apparently you have two different fields. You want to treat them as one, which immediately gets you into trouble. Treat them as separate instead.

You have one enum-backed field Whatever with a fixed number of predefined options. Your logic-bound enum is among these.

And then you have another field wich you may refer to as CustomWhatever. This is just a tag no one but the user cares about, which defaults to empty or none. If you cannot have both, add one option "Custom" to the fixed set and only enable the custom field when the custom option is selected.

0

There are valid reasons for why you may choose to replace enums with something that is, in a certain sense, more flexible and better expresses your design. This is not something you'd apply everywhere (even in the same codebase), though; this is acknowledged in the introductory paragraphs of the Microsoft article you referenced:

However, this isn't a critical topic and in many cases, for simplicity, you can still use regular enum types if that's your preference.

However, the approach as shown in the article isn't really that much better than using the built-in enums. The thing is, the example they give is not really illustrative, as you already have most of those features with traditional C# enums (but perhaps it's more clunkier to use them), although it does show some extra features you may find desirable. I'll come back to this later.

So, what's wrong with traditional enums? When used in a controlled way, and when team members are disciplined about their usage - nothing really. Yeah, there are strange quirks (like the fact that you can cast an integer into an undefined enum value), but often this isn't really an issue.

They become a problem when you use them in a way that causes coupling. E.g., an enum will be used somewhere to perform some conditional behavior. But then, there's related conditional behavior in another part of the codebase, and another, and another; and what people do is they put the same enum checks in all those places, scattering this decision logic around. Furthermore, a lot of code is written in a way that makes it dependent on the specific details of each case, without much regard to how difficult it will be to change later. And if not kept clean, the code will only grow in time, and become harder and harder to understand on its own, without having to read through the whole method, or worse (e.g., it will have non-obvious dependencies, it will rely on variables declared 100 lines before, etc...)

With enums this is fairly easy. We would just perform a very natural check if (cardType == CardType.Visa). The point is: the values are all there on code to be checked.

So, if that's the only place where that check occurs, it's not a problem. But if you have similar checks in five more places (probably in seemingly unrelated files), when you have to change something, suddenly, you have to hunt down all of these occurrences, and even when you do, something is not going compile, or worse, it will compile, but something wont work - all because you changed some detail that shouldn't have had affected anything. Similarly, if you need to add a new case, you have to find all those checks, decide if this new case should even be added, then read the code to understand how to add the new logic in a way that works well with what's already there, etc. (This whole ordeal is one form of what's called "shotgun surgery"). Also, there could be a new non-obvious place where this check needs to be introduced - if you forget to add it, you introduce a bug, if you remember, you couple the code even further. Not to mention that all these interdependencies may get in your way if you try to do any kind of coarse-grained restructuring, or separate things into DLLs, etc.

So, that's what I mean by constrained/disciplined use of enums - the more experienced developers can sort of sense that something will lead to unwanted coupling, or that there already is coupling that needs to be managed, so they will change things up to improve the situation (that's under the assumption that this is a longer-lived project that needs to be kept maintainable, and that this is an important/active part of the codebase); there's little point to spending too much design effort on one-off stuff).

One way to deal with such issues is, as others have mentioned, the Strategy Pattern, which relies on polymorphism and well-designed interfaces to remove the decision logic from the immediate client code (basically pushing it somewhere up the call chain), while encapsulating the case-specific behavior in each ConcreteStrategy class. But to do this, you have to have a good grasp on the specific aspects of the domain (business logic) that relate to these cases, and to understand what's a common thread among them, and how it can be expressed through the Strategy interface. Then, ideally, any changes and extensions are localized, and extension points are well defined. It's really a design issue - if you pull this off correctly, it's a more clean, more maintainable way to model the domain.

The appeal of using an Enumeration Class (the alternative suggested in the article) mostly comes from it being a way to implement the Strategy pattern (while letting you still treat it as an enum if you need to - but be careful with this). If you scroll down, there's an reference to a post by Jimmy Bogard, under Additional Resources - see what he does near the end of his article, when he adds the ManagerType as a subclass of EmployeeType. Basically, the EmployeeType is not treated as just some flag/indicator, but it's a higher level concept that has business meaning and associated behaviors within that logical construct (with case-specific stuff handled by these subclasses). The code is then written in terms of this EmployeeType, but polymorphism allows you to pass in a subtype.

But even there, there's this remark:

Enumerations work well in a variety of scenarios, but can break down quickly inside your domain model. [...] This pattern shouldn’t replace all enumerations, but it’s nice to have an alternative.

(Note: Here, when he says enumerations, he means C# enums, not Enumeration Classes.)

So, again - you wouldn't apply this everywhere, just where it matters. (Another aspect of this is complexity - you want to use it in places where it removes more complexity then it introduces.)

Finally, to address your actual question. You said:

But with enumerations the values are dynamical and added by the user.

If you are referring to Enumeration Classes, that is not the idea - the values aren't added by the user (it's just that now they are full blown objects and can house the case-specific behavior associated with the value). True, you can leverage a similar structure, and allow for user-defined input, if, say, you specifically design a class that has behavior that can be parameterized by user input; but at that point, it doesn't really make sense to call this an enumeration.

In summary: if we use enumration classes instead of enum and if there is a business rule for a specific item that the user is adding dynamically, how can we verify and address that in a more reliable way than "checking a string"?

Well, first, Enumeration Classes aren't exactly the right tool for the job. Given possible input, and the nature of the associated business rules, you'd basically write some "outer" code that checks if the input is valid, etc., even if it comes from a database (this may be just a bunch of if-statements, or something more sophisticated), and then it would, based on this input, instantiate and/or call an "inner" layer business logic code that itself doesn't perform these checks (i.e. isn't written in terms of them, it just does its own thing). So, there's one place where the decision (case selection) takes place, instead of this being scattered around, and the core business logic itself doesn't rely on strings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.