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.
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.