Get the source code for this video for FREE → the-dotnet-weekly.ck.page/smart-enums Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
Okay, I get it that all companies are trying to obfuscate the code even though it is compiled but this ... this seems like a perfect example of overengineering. There's basically zero profit from your solution and it only adds huge overhead on the "enumeration". This discount example could be simplified with proper enum and extension methods or simple attributes. Both mentioned methods gives no overhead, no additional abstraction layers, no additional class files.. I really tried to find any reason for this but I can't. It's impossible to justify this approach
2 ปีที่แล้ว +6
Exactly my thought. I have tried to use Ardalis.SmartEnum, which is similar and I just cannot justify it. Enums are quite powerful because they are so simple. All I can see with SmartEnums is that they remove all the benefits, and adds complexity back. If simple static classes, extension methods and attributes is not enough, then what you want is probably not an enum in the first place.
So OOP always overengineering then, Mateusz? Because I don't think the example is that complicated really. I see this as a way to achieve an enum-like design, while being able to introduce behavior in form of data/methods on the class. Extension methods are an option, but then your logic lives in a different file from the enum. And you have the cognitive load of maintaining two files when you want to add a new enum value. With my approach, everything is in one file. And you can have abstract member, which *forces* you to define said member when adding new implementations.
@@MilanJovanovicTech Never said it was complicated. What I said is that is adds a ton of overhead compared to enums. Besides that, having additional functionality like a discount based on enumeration is a separate logic itself so based on SOLID principles it should be separated into its own functional class/file/method. You could potentially create enum with extension methods in one file and it would still be more readable, better maintainable and efficient. Not to mention how it could break the whole application because of the possibility to add the enumeration with the same name more than once. The issue will not be prompted during design, code or compile phase but during runtime. Adding all this makes a huge impact on performance when you're handling thousands of requests per second. I understand that the new trend in software development is to create more and more code not worrying about performance but attitude like this is the main reason we now have a simple communicators, notepads or other relatively compact applications requiring almost a GB of ram and possibly taking a ridiculous amount of CPU time. Promoting this kind of behaviors in my opinion is not the way we should follow. We should write efficient but clean and understandable code that would immediately inform us about the issues it can have.
I think you may be missing the point, which is understandable if you've never come across the situations where this sort of thing is really convenient. Also, this really has very little overhead to the point where I don't think you're using that term correctly. It also doesn't have that much abstraction: it's a fairly straightforward generic class that uses the "Curiously Recurring Template Pattern". I wrote something almost identical to this years ago, and the goal was to eliminate magic strings and to properly associate C# types with the enumerations you commonly see in database tables. Most databases will have multiple tables that define enumerations. If you read a value of '1' from some field in some record in some other table, you will typically need to have magic logic that handles the case when that value is '1' or '2' or '3', etc, and that logic will be littered with magic strings. The developer also just has to know that it effectively represents an enum. There's also the matter that each enum value represents a different kind of thing that requires different business rules to handle. You could define a vanilla enum and then try to cast those values to that enum, but that is duplicating what is already in the database, and it doesn't really improve the design and readability of the code very much. But if you use the generic enum described in this video, you can make all of the magic strings and magic logic go away, and every enum value will have a strong type with the business logic associated with that type. It can be a very convenient solution.
@@thomasreasoner6253 You just described few of the possible usecases for ORM or mapping in general which is totally different from the example above. There are no "magic strings" these are types values that during runtime are treated as integers (something computers are good at). Youre trying to prove the point here that doesnt exist
@@MilanJovanovicTech so why exactrly do you need to achieve enum-like behavior? If your objects doents fits in enum, don't use it like enum) Otherwise it's just unnesessary complexity. Good for youtube, but bad for real projects)
We have been using smart enums in production for some years now. I can say that it has been a joy to refactor some areas of our domain and push behaviour into our types (smart enums).
Thanks for sharing this idea. This is a nice approach. I would still say this is a bit overkill. When you need to implement new credit card you have to go back to the CreditCard class and modify it. I believe this can be modified a bit to make it more solid friendly.
Essentially creating a way to work with classes that would inherit from a similar parent in an enum style way. I like it for adding some structure to the code base in working with inherited classes. Thanks
Hi Milan, firstly, allow me thank you for your high quality content. I think what you are trying to do here is to discover SUM types that C# has refused to add to the language. There is a library though LanguageExt that supports them, it generates most of the code for you too. Thank you
Genuinely clear and concise video, but this seems over engineered. If you need something more than enums, use classes, which is what you did. Great implementation demonstrated, but this should have been a more "Why you should use this over enums" and give better context and real world examples.
It's tough to also come up with real-world examples in the context of 10-20min video. But I do have a use case for it in a future video, for creating and seeding Roles
I've used a setup very similar to this to implement string-based enums, as a way to combat primitive obsessions, or "magic strings". It's also good to add implicit converters to and from strings. I'd imagine that using this with complex classes, instead of primitives, could lead to issues with polymorphism, and hierarchy, if you don't create team-wide rules as to their usage. I wonder how much of the bloat within this could be extracted via source generators, using attributes on the public static fields(/properties?) to set discount values. Personally, I don't think that an "Enum" should be used to discriminate complex information, or business logic, so a source generator that allows basic distinct information to be assigned doesn't stretch it too far. Otherwise, a dictionary wrapper, or repo would be more suitable. The thought of piling business logic into ever-more-bloated private nested classes just leaves a nasty taste in my mouth.
They don't necessarily have to be private classes, but yes, the number of classes can significantly grow. I try to not overuse this, but I find it very practical in certain situations.
Is primitive obsession that bad? The counter part is having magic numbers which is much worse than this. A Data Class is helpful tool in the end, not a sin.
@@MilanJovanovicTech It feels like every solution pattern needs a section for "when not to use it" because ppl always try to use it for whatever they can get their hands on.
In my mind, the real benefit of rich enums (in other languages) is that they’re value semantic. That is, a performant way to represent complex data structures. As soon as you use classes you’re using the heap, and to boot you’re using reflection. Kind of defeats the point IMO.
@chris simpson but you're not creating new references. There are a few startup allocations on the heap, but they will live for the whole applications lifetime. There is no GC pressure coming from them. As already answered the Reflection is only called once per type, but you could also just add it to the dictionary in the constructor.
I'm not sure what you call "smart enum" but this is 1) Mislabeled - there's nothing here that's either smart or enum or 2) Unnecessary and overengineered. In any case, I can assure you that this would be a no-go for anyone in our org. If anything, this is more of an abstract factory with a bit of business logic but, unless the business logic is more than just a property, you would definitely get your PR rejected trying to merge this implementation. Software engineering is already complex. There's elegance in simplicity.
I understand your view. Suggestion: take this as a tool to have in your toolbox. It might come a day where you are faced with a scenario that fits smart enums. This has already happened in some of our production projects, mainly the ones with a vast amount of business logic. We were all very pleased with the final result.
Hey Jesus, I'm sure your comment has good intentions. But do you realize what I try to do is explore concepts in present topics I find interesting? This is obviously an intentionally trivial example, that in no way represents a real use case. I believe that much is obvious. Neither am I suggesting that people go out there and replace C# enums with the Enumeration class for _every_ use case. But I had a lot of success applying this pattern in a few projects, and I find it worthwhile to share with my audience.
I'm sure that there are cases where such an implementation would be useful. This, IMO, isn't entirely clear in the video. It's not fixing an actual problem. A stripped down version isn't doing it any favor either. Add a bit more logic and you can no longer call that neither smart nor enum. At best, this is a controversial approach, and it shows. Unfortunately there's an inherent risk of people taking this kind of stuff and adding it to any code base (especially with this kind of exposure), thereby adding unnecessary complexity (I'm guilty of doing this myself). A better approach would be to 1) not only to explain the advantages but *also* the disadvantages of such an implementation and 2) provide a better use case or scenario. Let's not forget that we need to use the right tool for the job. Having a clear understanding of the tool is more critical than mindlessly writing code.
Brutal overkill, using reflections, logic coupling (credit card type is not really tightly bound to discounts - discounts could probably change overtime, and maybe based on other factors), while you can get code, with exactly the same behavior and even faster runtime creating normal enum and then creating GetDiscount extension method for it, that is a simple switch case. Much cleaner. I usualy abuse partial class for this scenarios and have one Extension class per namespace and write all stuff, that is not necesarily a part of object into a partial Extension class at the end of file.
I can never fathom why people latch onto the concrete example when trying to critique the video. From my perspective, the point here is illustrating the concept of Enumeration class. The CreditCard example can be irrelevant, but I found it simple enough so that people can follow along. Partial classes make me 🤮
@@MilanJovanovicTech yeah, I get your point about partial classes. And what I proposed is obviously abusing it, as I mentioned. I also get your point about latching on concrete example. Nevertheless, the point here was that you can get it much cleaner and easy with extension methods, which is exactly what they are here for. Enums are only meant for marking some stuff based on static premise (days of week). They are not supposed to be objects by themselves. They are basically meant to be a food for switch case statement. If you want to have proper classification of entity of any type, then sure, use classes. If you want for whatever reason to have only one class of each type in your app, then sure, use singletons. If you expect for whatever reason the collection of the stuff you want your classification to be extensible, you should not use Enums at all in the first place. I mean, in a way, this is a interesting pattern, and some people in the comments even found a name for it, and I am sure there is plenty use for it. I am just saying it is overengineering in plenty of cases. And many people might actually need a simpler solution for similar problem and here I am, telling them, not to follow your example.
You're really good author and I watch your videos every day:) But this article is a little overhead. In this case I would prefer an extension method for this Enum. If I would afraid to forget implement one of case after change enum I'll write some test to check that all defined enum values are hanled into my extesions. But I agree with you in case when we need to many consistent logic per each enum value. Good luck!
I used a similar pattern but much simpler without a base class, just a sealed class with a private parameterless constructor and public static get-only properties. I think the code you've written is massively over-engineered. When it comes to adding some simple logic to an enum, using an extension method is also a solution.
I really like the idea behind all of this but i think it could be better achieved using records to gain most of the functionality with less code (or rather, the compiler generates the code for you with records). For me, a simple: public record CreditCard{ Public string Name; Public int Value; Public double Discount; Public static readonly CreditCard Normal => new(1, "Normal", 0.01); // other types as necessary } This type of simple implementation is plenty enough for me, and small enough to fit into one file.
Java has 'rich enums' and i giggle every time a junior dev finger-traps himself with it. Bonus points if they are also somehow represented in the database and have to be kept in sync!
Nice approach for rich enum type, but I would rather use CustomAttriubtes on each enums. And lazy load those informations to static dictionary, if it is necessary.
That's an option of course, I just wanted to show the approach with an abstract member. Probably would have made more sense with an abstract method with some logic.
Hey Milan How to map types derived from smart Enum to DTOs and how to compose the DTO itself to avoid duplicating these static fields? What practice would you recommend?
Watched the video a second time and even though there is a bit of code needed "as infrastructure" (the base class) as well as for the implementation of a concrete use case I definitively like the idea of "avoiding bugs by design". Looking at the controversial discussions in the comments I wonder whether some missed the key achievement: the logic is no longer decoupled from the enum values. Means, as you stated in your introduction, with the initial implementation one could forget to update the switch statement when a new enum value is added. With the new approach you just add another abstract method/property and once everything compiles again you are done and you can be sure that the rest of the software will work as expected. No need to run the full regression test suite of the entire software system. this is "fail fast" to the extreme. I think this benefit is worth some extra code. Personally I would favor a more "functional programming approach" over an OOP approach which would look like this: th-cam.com/video/IoUsyEyWW0k/w-d-xo.html
Yes! You get the point. I was surprised by the outburst of comments on this video 😅 I wonder if we combine my approach with Match extension method we get something really nice 🤔
1. You can add transitions. For example Status enums. ( New -> Confirm, Edit, Delete; Confirm -> Delete; and so on) I found it useful in doc processing to get next Status transitions via one line of code in several places (doc.Status.GetNextStatuses();) 2. Serialization/Deserialization "musthave" 3. Localization for enums also can be abstract CreditCard.LocalizedName. 4. Add support EFCore or Dapper. I didn't find best solution, so in Entity model I have int property and cast it by AutoMapper domain.Status.FromValue(entity.Status). But I have projection error with this solution. Maybe there are more elegant way
I appreciate what you're trying to achieve. But all this code feels like compensating for the lack of certain language features. (Namely algebraic types and analysis of switches for completeness). And what you end up with is a mountain of boilerplate and less performant, more complicated code. Less performant because virtual function calls are slower than non-virtual ones, because dictionary lookups are slower than just using value literals and iterating over the values (in the FromName method) is even slower. The static dictionary and static fields also increase your base memory footprint, and generating the dictionary via Reflection increases startup, even if Reflection is way faster than people give it credit for (at least in C#). I know that performance isn't the be-all and end-all of programming and that the performance cost of this is a non-issue in 99.9% of cases, but it still irks me the wrong way cause one could have these abstractions and safety guarantees without any performance cost and without the boilerplate and complexity. I am also generally not a fan of bundling behavior together with the data. Theoretically one could even have it in C# if they'd spend the time to write custom code analyzers and enforced analyzer errors on build, which C# thankfully has build in. Good Video nonetheless.
@@MilanJovanovicTech I love DDD, I think it's essential for any larger code base. I am just not too big a fan of OO, because I believe it to be very cumbersome and complex, with weak tools for abstraction. Abstractions you need to model your domain. The reason I care about the performance cost even though it practically doesn't matter is that better solutions already exist elsewhere and we could have them in C#.
From Where do you learn all these things can share us link or book name. Like i cant even imagine to write a code in such a beautiful way. please help us to acknowledge from where one should practise such great learnings like you share with us
Looks good, would've been nice to add a solution for EFCore and Json Serialization/Deserialization which is a major paint point with those kind of implementations
I wrote almost this exact thing 5 years ago. I'm a little shocked how similar this code is to mine. Is this a common idea or pattern that I'm not aware of?
-1 You never moved Premium, Platinum, etc strings to a Enum -2 In C# we already have a classes (data classes) and structs (which effectively are classes in practice)
there are a lot of issues with this code. I tried following through and writing the code using my VS 2019 environment and it didn't work on many different things first when I tried to follow your convention of removing the name space curly braces namespace SmartEnum{} and replace it with SmartEnum; that failed. when I tried using protected init; I was unable to When I tried to use TENum? it also threw an exception I think these are all configuration issues. but you should address them before starting the video so that we can follow along and use your code thanks for the idea but can you provide some help in rectifying these issues?
I only see abstraction and inheritance. The only thing close to an enum is that you have a static instance for each credit card type and the equality was changed to value and not reference. Maybe you can list some good real world examples on when to use this and not with a credit card?
Try to look beyond the credit card example, and when you could benefit from a custom Enumeration. I've had a few use cases where I needed behavior on top of an enum, usually in the form of some method that computes something.
I think I may be missing something, but the static Enumerator variable will be overwritten in memory each time you call a different child class of Enumerator?
Thank you Milan for the video, really interesting topic and a good tool to have in any developer's toolbox. I wanted to ask if you can make a video on test the Domain layer in Domain Driven Design, I'm have so much trouble testing my domain models (aggregates / entities) since the domain model doesn't have setters (private) and the constructor is also hidden, IFixture didn't help me and i had to write long tests and with time tests are becoming more and more harder to write thanks a lot and really appreciate your efforts
For creating a new instance, you need to expose something. Either a constructor, or a static method. You don't need to test setters, but behavior. And you can do that with methods.
@Sam Verify -> If you want to test Domain Object it needs to have constructor (or public Create method) Then in test project you can create builder pattern for it
I found this over engineered just for enum. Basically, enum has all out of the box methods for such cases and it violates some of OOP which is composition over inheritance.
There is one additional advantage you didn't mention in that video. Let's say, you have a project that should be highly extendable. For example, you have got a base project with your common code. And you have a variation of your base project for Customer B that needs more Enum values. With a normal Enum, you can't extend the origin Enum in the customer B project. It's just not possible, since the Enum lives in your common project. There are some possibilities with (int) parsing but that's just bad practice. With the "smart" Enum approach, you can just add a CustomerBCreditCard : CreditCard class, add additional public static readonly Enum values for CustomerB like "public static readonly CreditCard Diamond" and everything will just work fine. Even the reflection dictionary will collect the new additional values and parsing will just work as before. Just wanted to mention this "huge" additional advantage. Of course, you need to add that kind of support but it's easy.
Cool solution. I am now having difficulty implementing it in my API. I'm using auto mapper and some abstractions like repository and unit of work. The scenario is as follows: The customer can have two subscription plans, the pro and the free plan. This is reported via json: {"ClientName":"john Doe", "Subscription":"Free"}. If anyone can help me, please adapt the logic would be incredible.
@@MilanJovanovicTech Exactly. Actually, I'll try to be clearer. I have a client model that has the 'subscription' attribute, and this attribute is an intelligent enum (it applies what was shown in the video). My challenge is adapting its logic with serialization (JSON) and AutoMapper (DTO). This is because to create the attribute, it would be like this: 'var subscription = SubscriptionPlans.FromName("Free");' I need to call the 'FromName' method, so I probably need to make changes in AutoMapper or serialization, right? I'm not sure if I managed to be clear, I hope so. Thank you for your time and dedication in sharing knowledge!
@@MilanJovanovicTech I have a software in my regular job where I might just refactor based on this. There is so much business logic based on an enum, that I think it would make the code cleaner.
I tend to have them in Utils project that should not have any dependencies in the solution. But of course if you have enums that are only used in a single project then you should have them in that project.
@@MilanJovanovicTech in enterprise. I think having them in one repository will make it easy to locate them for developers right? I am getting confuse for implementing the best practice. Thanks
Great feature, I used similar for value objects from limited range of possibilities. Milan, do You have any tips for vertical slice architecture in Clean Architecture, I am thinking about it... Should I slice it in both core layers separately? How to divide mutual operations for only couple of many slices? I would be really grateful for advices :)
@Milan Jovanović I will watch Jimmy's Boggard video and maybe find my answer there without bothering You. In other cases I will address you with concrete examples. Thanks a million:)
Good job, You can take care of JsonSerialization/Deserialization as a suggestion. (The official implementation supports that). Maybe EFCore default mapping is another concern too.
Not sure why would I need this, especially with int backing field as magic number, but I'm wondering if you tried to use the new 'static abstract' keywords for a card value
Thanks for the video! Some thoughts about using reflection to get our defined enum values, since we have a very limited amount of instances,, so in this case, in the base enumeration class we can add each instance to the dictionary (we need to use downcast like (TEnum)this but since we have type constraints, we can consider this cast as safe ) and the only problem we still have - static fields is not initialized before we call the actual type, it could be solved with System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TEnum).TypeHandle); in the base static constructor I'm not pretending that it's a better solution, just as an alternative to reflection which is not always available...
No, I think this is really bad. You took a simple enum and turned it into something more like an abstract factory. Stuff like that would be the first thing marked for refactoring in a code review in my company. In this example, keep the actual enum, add an extension method for the enum that creates the credit card classes. Less code, easily understandable by junior engineers and most importantly it requires no inheritance.
eShopOnContainers is a gold mine, eh? :> Now do a serialization, deserialization and db storage of those enums. Just to feel real cost of implementation.
@@MilanJovanovicTech That's the point. You've traded 8 lines of code for 2 abstract clases and 3 concrete ones, plus a need for custom converters for what is usually a trivial operation. Don't get me wrong, looks cool, but in reality it's very niche because of the implementation effort.
Ctor gets called for every instantiation. In Milans case (with it the Enumeration field being static) the CreateEnumerations Method only gets called once at application startup, where reflection of this size has little to no perf impact. You would also be in danger of forgetting to add new instances in the ctor if you did it your way.
@@benlewies8828 its only for api code. When you make a change to your api to add/remove/modify an enum and you tell your customers this is an enum. Then they are bound to a specific set of values. If they havent changed their code then they will get an error when parsing. This is extremely bad if you are maintaining systems where you dont want to affect your customers. Also, it doesnt work well if you are versioning unless you have a specific enum for every version which is cumbersome. It is better to just use a text value of the enum.
dont kill enums for class instance garbage, you wasted lots of memory and created performance issue. let ENUM type be just ENUM. NO need to create videos on everything ...
Get the source code for this video for FREE → the-dotnet-weekly.ck.page/smart-enums
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
is it simplier ?
@@trongphan6197 What exactly?
10:12 CreateEnumerations() method is overhead. You can push each enum member to the dictionary in Enumeration constructor
And initialize it how many times?
@@MilanJovanovicTech Once. Each time you create new enum member constructor add it to dictionary - once for each member
@@janedoe6182 That's what I thought as well.
Okay, I get it that all companies are trying to obfuscate the code even though it is compiled but this ... this seems like a perfect example of overengineering. There's basically zero profit from your solution and it only adds huge overhead on the "enumeration". This discount example could be simplified with proper enum and extension methods or simple attributes. Both mentioned methods gives no overhead, no additional abstraction layers, no additional class files..
I really tried to find any reason for this but I can't. It's impossible to justify this approach
Exactly my thought. I have tried to use Ardalis.SmartEnum, which is similar and I just cannot justify it.
Enums are quite powerful because they are so simple. All I can see with SmartEnums is that they remove all the benefits, and adds complexity back.
If simple static classes, extension methods and attributes is not enough, then what you want is probably not an enum in the first place.
So OOP always overengineering then, Mateusz?
Because I don't think the example is that complicated really.
I see this as a way to achieve an enum-like design, while being able to introduce behavior in form of data/methods on the class.
Extension methods are an option, but then your logic lives in a different file from the enum.
And you have the cognitive load of maintaining two files when you want to add a new enum value.
With my approach, everything is in one file. And you can have abstract member, which *forces* you to define said member when adding new implementations.
@@MilanJovanovicTech Never said it was complicated. What I said is that is adds a ton of overhead compared to enums. Besides that, having additional functionality like a discount based on enumeration is a separate logic itself so based on SOLID principles it should be separated into its own functional class/file/method. You could potentially create enum with extension methods in one file and it would still be more readable, better maintainable and efficient. Not to mention how it could break the whole application because of the possibility to add the enumeration with the same name more than once. The issue will not be prompted during design, code or compile phase but during runtime. Adding all this makes a huge impact on performance when you're handling thousands of requests per second.
I understand that the new trend in software development is to create more and more code not worrying about performance but attitude like this is the main reason we now have a simple communicators, notepads or other relatively compact applications requiring almost a GB of ram and possibly taking a ridiculous amount of CPU time. Promoting this kind of behaviors in my opinion is not the way we should follow. We should write efficient but clean and understandable code that would immediately inform us about the issues it can have.
I think you may be missing the point, which is understandable if you've never come across the situations where this sort of thing is really convenient. Also, this really has very little overhead to the point where I don't think you're using that term correctly. It also doesn't have that much abstraction: it's a fairly straightforward generic class that uses the "Curiously Recurring Template Pattern".
I wrote something almost identical to this years ago, and the goal was to eliminate magic strings and to properly associate C# types with the enumerations you commonly see in database tables. Most databases will have multiple tables that define enumerations. If you read a value of '1' from some field in some record in some other table, you will typically need to have magic logic that handles the case when that value is '1' or '2' or '3', etc, and that logic will be littered with magic strings. The developer also just has to know that it effectively represents an enum. There's also the matter that each enum value represents a different kind of thing that requires different business rules to handle. You could define a vanilla enum and then try to cast those values to that enum, but that is duplicating what is already in the database, and it doesn't really improve the design and readability of the code very much. But if you use the generic enum described in this video, you can make all of the magic strings and magic logic go away, and every enum value will have a strong type with the business logic associated with that type. It can be a very convenient solution.
@@thomasreasoner6253 You just described few of the possible usecases for ORM or mapping in general which is totally different from the example above. There are no "magic strings" these are types values that during runtime are treated as integers (something computers are good at). Youre trying to prove the point here that doesnt exist
Seems like solution which solve a problem thas doesn't need to be solved. If you need more than just an enum, use classes or structures.
He is using classes:)
@@vincentvega5104
Okay...
"...use classes which not pretend to be an enums"
With all this extra implementing interfaces and useless methods.
As Vincent pointed out, I am using classes 😅
With a few sprinkles on top to achieve an "enum"-like design.
What's so wrong about this approach?
@@MilanJovanovicTech so why exactrly do you need to achieve enum-like behavior? If your objects doents fits in enum, don't use it like enum)
Otherwise it's just unnesessary complexity. Good for youtube, but bad for real projects)
We have been using smart enums in production for some years now. I can say that it has been a joy to refactor some areas of our domain and push behaviour into our types (smart enums).
Thanks for sharing this idea. This is a nice approach. I would still say this is a bit overkill. When you need to implement new credit card you have to go back to the CreditCard class and modify it. I believe this can be modified a bit to make it more solid friendly.
The example is trivial, so it seems overkill. But think about the concept in broader terms 😁
I agree..
Getting a lot of stick for this video in the comments which is uncalled for. I found it interesting, thanks
I think it's part of expressing your opinions publicly. There will always be people that disagree. 😅
Essentially creating a way to work with classes that would inherit from a similar parent in an enum style way. I like it for adding some structure to the code base in working with inherited classes. Thanks
Glad you liked it
Hi Milan, firstly, allow me thank you for your high quality content. I think what you are trying to do here is to discover SUM types that C# has refused to add to the language.
There is a library though LanguageExt that supports them, it generates most of the code for you too. Thank you
Awesome, let me take a look 😁
Woa. It's quite a good example around many practices and aspects of OOP.
Thanks
Genuinely clear and concise video, but this seems over engineered. If you need something more than enums, use classes, which is what you did. Great implementation demonstrated, but this should have been a more "Why you should use this over enums" and give better context and real world examples.
It's tough to also come up with real-world examples in the context of 10-20min video. But I do have a use case for it in a future video, for creating and seeding Roles
I've used a setup very similar to this to implement string-based enums, as a way to combat primitive obsessions, or "magic strings". It's also good to add implicit converters to and from strings.
I'd imagine that using this with complex classes, instead of primitives, could lead to issues with polymorphism, and hierarchy, if you don't create team-wide rules as to their usage. I wonder how much of the bloat within this could be extracted via source generators, using attributes on the public static fields(/properties?) to set discount values. Personally, I don't think that an "Enum" should be used to discriminate complex information, or business logic, so a source generator that allows basic distinct information to be assigned doesn't stretch it too far. Otherwise, a dictionary wrapper, or repo would be more suitable. The thought of piling business logic into ever-more-bloated private nested classes just leaves a nasty taste in my mouth.
They don't necessarily have to be private classes, but yes, the number of classes can significantly grow.
I try to not overuse this, but I find it very practical in certain situations.
Is primitive obsession that bad? The counter part is having magic numbers which is much worse than this. A Data Class is helpful tool in the end, not a sin.
This is awesome! I was thinking about this for some days, but this seems to be the closest to the java's enums! Will try it today :)
Go for it! There's also a NuGet package that does this
For most of us, this would look like unnecessary overcomplication. But great trick to have nevertheless. Thanks for the video.
I found a use for this in a few occasions. It shouldn't be used for every enum in the codebase, that's for sure.
oh god I'm lost this is very complicated solution
It makes sense if you need rich behavior in your enums. If all you need is a simple enum, it's certainly too much 😅
@@MilanJovanovicTech It feels like every solution pattern needs a section for "when not to use it" because ppl always try to use it for whatever they can get their hands on.
We're breaking OCP here. Feels like a ValueObject lib would handle this just as well, or better.
Do we have to be strict about all the principles out there?
Though the implementation is nice, it is YAGNI for most of the time.
There is a reason we have simple enum types. 😉
There's also a situation where simple enums aren't enough
In my mind, the real benefit of rich enums (in other languages) is that they’re value semantic. That is, a performant way to represent complex data structures. As soon as you use classes you’re using the heap, and to boot you’re using reflection. Kind of defeats the point IMO.
The reflection only runs once per type, so no real performance hit there
@chris simpson but you're not creating new references. There are a few startup allocations on the heap, but they will live for the whole applications lifetime. There is no GC pressure coming from them.
As already answered the Reflection is only called once per type, but you could also just add it to the dictionary in the constructor.
I'm not sure what you call "smart enum" but this is 1) Mislabeled - there's nothing here that's either smart or enum or 2) Unnecessary and overengineered. In any case, I can assure you that this would be a no-go for anyone in our org.
If anything, this is more of an abstract factory with a bit of business logic but, unless the business logic is more than just a property, you would definitely get your PR rejected trying to merge this implementation.
Software engineering is already complex. There's elegance in simplicity.
I understand your view. Suggestion: take this as a tool to have in your toolbox. It might come a day where you are faced with a scenario that fits smart enums. This has already happened in some of our production projects, mainly the ones with a vast amount of business logic. We were all very pleased with the final result.
Hey Jesus, I'm sure your comment has good intentions. But do you realize what I try to do is explore concepts in present topics I find interesting?
This is obviously an intentionally trivial example, that in no way represents a real use case. I believe that much is obvious.
Neither am I suggesting that people go out there and replace C# enums with the Enumeration class for _every_ use case.
But I had a lot of success applying this pattern in a few projects, and I find it worthwhile to share with my audience.
I'm sure that there are cases where such an implementation would be useful. This, IMO, isn't entirely clear in the video. It's not fixing an actual problem.
A stripped down version isn't doing it any favor either. Add a bit more logic and you can no longer call that neither smart nor enum. At best, this is a controversial approach, and it shows.
Unfortunately there's an inherent risk of people taking this kind of stuff and adding it to any code base (especially with this kind of exposure), thereby adding unnecessary complexity (I'm guilty of doing this myself).
A better approach would be to 1) not only to explain the advantages but *also* the disadvantages of such an implementation and 2) provide a better use case or scenario.
Let's not forget that we need to use the right tool for the job. Having a clear understanding of the tool is more critical than mindlessly writing code.
i will never finish my project, you keep giving some new information, i keep updating my project. Please let me finish my project))))
Don't overengineer it!
Subscribed. I liked the way you explain things in a easy manner.
Thanks a lot, I have some interesting videos coming in the next few weeks 😁
@@MilanJovanovicTech looking forward to that.
Brutal overkill, using reflections, logic coupling (credit card type is not really tightly bound to discounts - discounts could probably change overtime, and maybe based on other factors), while you can get code, with exactly the same behavior and even faster runtime creating normal enum and then creating GetDiscount extension method for it, that is a simple switch case. Much cleaner. I usualy abuse partial class for this scenarios and have one Extension class per namespace and write all stuff, that is not necesarily a part of object into a partial Extension class at the end of file.
I can never fathom why people latch onto the concrete example when trying to critique the video.
From my perspective, the point here is illustrating the concept of Enumeration class. The CreditCard example can be irrelevant, but I found it simple enough so that people can follow along.
Partial classes make me 🤮
@@MilanJovanovicTech yeah, I get your point about partial classes. And what I proposed is obviously abusing it, as I mentioned. I also get your point about latching on concrete example. Nevertheless, the point here was that you can get it much cleaner and easy with extension methods, which is exactly what they are here for.
Enums are only meant for marking some stuff based on static premise (days of week). They are not supposed to be objects by themselves. They are basically meant to be a food for switch case statement.
If you want to have proper classification of entity of any type, then sure, use classes. If you want for whatever reason to have only one class of each type in your app, then sure, use singletons.
If you expect for whatever reason the collection of the stuff you want your classification to be extensible, you should not use Enums at all in the first place. I mean, in a way, this is a interesting pattern, and some people in the comments even found a name for it, and I am sure there is plenty use for it. I am just saying it is overengineering in plenty of cases. And many people might actually need a simpler solution for similar problem and here I am, telling them, not to follow your example.
You're really good author and I watch your videos every day:) But this article is a little overhead. In this case I would prefer an extension method for this Enum. If I would afraid to forget implement one of case after change enum I'll write some test to check that all defined enum values are hanled into my extesions. But I agree with you in case when we need to many consistent logic per each enum value. Good luck!
Note that this is the strategy pattern, pretty much. With a way to make it look like an enum. It has its uses.
It's a very interesting concept by the way. Wish C# could have something similar out of the box like some sort of smart enums exist in Java
We're not that lucky 😁
I prefer to create a custom attribute where I can assign data to my enum members
Interesting
Just use Dictionary
What will that bring us?
@@MilanJovanovicTech less code, same result credit[premium] will give discount for premium
That, kids, is a way to implements Abstract Factory Design Pattern.
Great video!
That could be one way to look at it, but a few pieces are missing to make it a real factory. Don't you think?
I can see this being extended to be used in conjunction with caching and persistence, and for use with the strategy pattern.
Strategy pattern is the perfect use case
I used a similar pattern but much simpler without a base class, just a sealed class with a private parameterless constructor and public static get-only properties.
I think the code you've written is massively over-engineered.
When it comes to adding some simple logic to an enum, using an extension method is also a solution.
The only reason it's "over-engineered" is because I'm creating something generic
I really like the idea behind all of this but i think it could be better achieved using records to gain most of the functionality with less code (or rather, the compiler generates the code for you with records). For me, a simple:
public record CreditCard{
Public string Name;
Public int Value;
Public double Discount;
Public static readonly CreditCard Normal => new(1, "Normal", 0.01);
// other types as necessary
}
This type of simple implementation is plenty enough for me, and small enough to fit into one file.
Might have to revisit this idea with records :)
This way the principle of OCP is violate!
Thanks for this video!
My pleasure!
Java has 'rich enums' and i giggle every time a junior dev finger-traps himself with it. Bonus points if they are also somehow represented in the database and have to be kept in sync!
I have to see what Java can do 😁
Nice approach for rich enum type, but I would rather use CustomAttriubtes on each enums.
And lazy load those informations to static dictionary, if it is necessary.
That's a cool idea. Any example out there with a similar implementation?
Well explained 👏 thanks
My pleasure 😁
In my eyes you crossed the river to get water, this being merely a coding exercise. Think the implemtation could be much simpler.
Perhaps, when you take this example into consideration. But a rich enum is very useful in some places, I think it's a nice tool to know about.
How about just assigning the Discount property with a getter only from a private constructor? Thereby you don't need three different classes.
That was going to be my suggestion. Just make the constructor for CreditCard take a discount value. Then you don't even need the inherited classes.
That's an option of course, I just wanted to show the approach with an abstract member. Probably would have made more sense with an abstract method with some logic.
Hey Milan
How to map types derived from smart Enum to DTOs and how to compose the DTO itself to avoid duplicating these static fields?
What practice would you recommend?
You could just return the Id or just the Name? Both are enough to create an instance
Watched the video a second time and even though there is a bit of code needed "as infrastructure" (the base class) as well as for the implementation of a concrete use case I definitively like the idea of "avoiding bugs by design". Looking at the controversial discussions in the comments I wonder whether some missed the key achievement: the logic is no longer decoupled from the enum values. Means, as you stated in your introduction, with the initial implementation one could forget to update the switch statement when a new enum value is added. With the new approach you just add another abstract method/property and once everything compiles again you are done and you can be sure that the rest of the software will work as expected. No need to run the full regression test suite of the entire software system. this is "fail fast" to the extreme. I think this benefit is worth some extra code. Personally I would favor a more "functional programming approach" over an OOP approach which would look like this: th-cam.com/video/IoUsyEyWW0k/w-d-xo.html
Yes! You get the point. I was surprised by the outburst of comments on this video 😅
I wonder if we combine my approach with Match extension method we get something really nice 🤔
@@MilanJovanovicTech sounds like "Smart Enums - Part 2" ;-)
That nice thank you for share this❤
But we can to create thatethod create enumeration without reflection in constructore class
How?
@@MilanJovanovicTech when constructor with two parameter is called add parameters to that Dictionary :D
Going from NY to Boston through LA.
Did you make it?
1. You can add transitions. For example Status enums. ( New -> Confirm, Edit, Delete; Confirm -> Delete; and so on) I found it useful in doc processing to get next Status transitions via one line of code in several places (doc.Status.GetNextStatuses();)
2. Serialization/Deserialization "musthave"
3. Localization for enums also can be abstract CreditCard.LocalizedName.
4. Add support EFCore or Dapper. I didn't find best solution, so in Entity model I have int property and cast it by AutoMapper domain.Status.FromValue(entity.Status). But I have projection error with this solution. Maybe there are more elegant way
4. EF Value Converter is what I use, works great
I appreciate what you're trying to achieve. But all this code feels like compensating for the lack of certain language features. (Namely algebraic types and analysis of switches for completeness).
And what you end up with is a mountain of boilerplate and less performant, more complicated code.
Less performant because virtual function calls are slower than non-virtual ones, because dictionary lookups are slower than just using value literals and iterating over the values (in the FromName method) is even slower.
The static dictionary and static fields also increase your base memory footprint, and generating the dictionary via Reflection increases startup, even if Reflection is way faster than people give it credit for (at least in C#).
I know that performance isn't the be-all and end-all of programming and that the performance cost of this is a non-issue in 99.9% of cases, but it still irks me the wrong way cause one could have these abstractions and safety guarantees without any performance cost and without the boilerplate and complexity. I am also generally not a fan of bundling behavior together with the data.
Theoretically one could even have it in C# if they'd spend the time to write custom code analyzers and enforced analyzer errors on build, which C# thankfully has build in.
Good Video nonetheless.
You must not be a fan of DDD also 🤔
@@MilanJovanovicTech I love DDD, I think it's essential for any larger code base. I am just not too big a fan of OO, because I believe it to be very cumbersome and complex, with weak tools for abstraction. Abstractions you need to model your domain.
The reason I care about the performance cost even though it practically doesn't matter is that better solutions already exist elsewhere and we could have them in C#.
Nice video. i will use this enumeration class. Thank you 😊
Thanks, glad you liked it!
From Where do you learn all these things can share us link or book name. Like i cant even imagine to write a code in such a beautiful way. please help us to acknowledge from where one should practise such great learnings like you share with us
I don't know, really. Lots of sources. Pluralsight courses. Clean coding book. Refactoring book.
Looks like strategy pattern + a storable identifier to me. Can you enforce exhaustive pattermatching switch expressions with this?
I'm not sure about that, gotta check
Looks good, would've been nice to add a solution for EFCore and Json Serialization/Deserialization which is a major paint point with those kind of implementations
If all the data is static, it can be enough to store the Value integer with value converters
Can be a separate video 👌
Steve Smith's SmartEnum nuget package does this.
Cool!
What would be the difference between a strongly typed enum and an Value Objects?
Intent.
ugh. code horror. when a bug comes, and people look at this type of code they panic. some things are better in KISS mode, IMHO.
What is horror code here exactly? 🤔
I wrote almost this exact thing 5 years ago. I'm a little shocked how similar this code is to mine. Is this a common idea or pattern that I'm not aware of?
It's a common idea
-1 You never moved Premium, Platinum, etc strings to a Enum
-2 In C# we already have a classes (data classes) and structs (which effectively are classes in practice)
I guess I did -1 and -2 combined
there are a lot of issues with this code. I tried following through and writing the code using my VS 2019 environment and it didn't work on many different things
first when I tried to follow your convention of removing the name space curly braces
namespace SmartEnum{}
and replace it with SmartEnum;
that failed.
when I tried using protected init; I was unable to
When I tried to use TENum? it also threw an exception
I think these are all configuration issues. but you should address them before starting the video so that we can follow along and use your code
thanks for the idea but can you provide some help in rectifying these issues?
Use .NET 6 or .NET 7
I only see abstraction and inheritance. The only thing close to an enum is that you have a static instance for each credit card type and the equality was changed to value and not reference. Maybe you can list some good real world examples on when to use this and not with a credit card?
Try to look beyond the credit card example, and when you could benefit from a custom Enumeration.
I've had a few use cases where I needed behavior on top of an enum, usually in the form of some method that computes something.
How would you do this, so it still works with the Flags attribute a normal enum can use?
Probably possible with writing some code to overload the | operator. But I'm not sure how fun that is going be to implement properly 😅
I think I may be missing something, but the static Enumerator variable will be overwritten in memory each time you call a different child class of Enumerator?
Each generic class is a different instance, so the static variable shouldn't be affected
Thank you Milan for the video, really interesting topic and a good tool to have in any developer's toolbox.
I wanted to ask if you can make a video on test the Domain layer in Domain Driven Design, I'm have so much trouble testing my domain models (aggregates / entities) since the domain model doesn't have setters (private) and the constructor is also hidden, IFixture didn't help me and i had to write long tests and with time tests are becoming more and more harder to write
thanks a lot and really appreciate your efforts
For creating a new instance, you need to expose something. Either a constructor, or a static method.
You don't need to test setters, but behavior. And you can do that with methods.
@Sam Verify ->
If you want to test Domain Object it needs to have constructor (or public Create method) Then in test project you can create builder pattern for it
I found this over engineered just for enum. Basically, enum has all out of the box methods for such cases and it violates some of OOP which is composition over inheritance.
Fair enough, I'm here to show ideas. You decide what works and doesn't work for you :)
I found the video present some useful ideas for storing additional data with the enum, which I have needed to do on a number of occasions.
There is one additional advantage you didn't mention in that video.
Let's say, you have a project that should be highly extendable.
For example, you have got a base project with your common code.
And you have a variation of your base project for Customer B that needs more Enum values.
With a normal Enum, you can't extend the origin Enum in the customer B project. It's just not possible, since the Enum lives in your common project. There are some possibilities with (int) parsing but that's just bad practice.
With the "smart" Enum approach, you can just add a CustomerBCreditCard : CreditCard class, add additional public static readonly Enum values for CustomerB like "public static readonly CreditCard Diamond" and everything will just work fine. Even the reflection dictionary will collect the new additional values and parsing will just work as before.
Just wanted to mention this "huge" additional advantage.
Of course, you need to add that kind of support but it's easy.
That's a very interesting extensibility option, it hasn't crossed my mind
Cool solution.
I am now having difficulty implementing it in my API.
I'm using auto mapper and some abstractions like repository and unit of work.
The scenario is as follows: The customer can have two subscription plans, the pro and the free plan.
This is reported via json: {"ClientName":"john Doe", "Subscription":"Free"}.
If anyone can help me, please adapt the logic would be incredible.
Parse on controller level?
@@MilanJovanovicTech
Exactly. Actually, I'll try to be clearer. I have a client model that has the 'subscription' attribute, and this attribute is an intelligent enum (it applies what was shown in the video).
My challenge is adapting its logic with serialization (JSON) and AutoMapper (DTO).
This is because to create the attribute, it would be like this: 'var subscription = SubscriptionPlans.FromName("Free");'
I need to call the 'FromName' method, so I probably need to make changes in AutoMapper or serialization, right?
I'm not sure if I managed to be clear, I hope so.
Thank you for your time and dedication in sharing knowledge!
Excellent video. Thanks for sharing this
Thanks, glad you liked it! 😁
I smushed subscribe button so hard that now it is stuck inside UI :D
Is that why I got 100 subscribers out of nowhere? You really smashed it good 😂
Very good strategy to have Enums with behavior. This goes perfectly for DDD architectures. Thanks for sharing Milan :)
I've used in DDD typed applications mostly
Hmmm so it's a Factory pattern that returns subclasses given an enum value. People already do that.
Who said they didn't? 🤔
How does this compare with Ardalis SmartEnum approach?
Pretty similar
Is this an implementation of the strategy pattern ?
In a way...
Maybe a better solution would be to use the abstract factory design pattern?
This is one way to implement something similar. I see it more as the strategy pattern
great video, thanks for sharing
Thanks, Rafael. Glad you liked it
Nice video, thanks for this.
You're among the few that enjoyed it. It seems I triggered a lot of people 😂
@@MilanJovanovicTech I have a software in my regular job where I might just refactor based on this. There is so much business logic based on an enum, that I think it would make the code cleaner.
@@majormartintibor Just weigh it carefully against the increase in complexity
That is a massive like
Glad you liked it 😁
In a solution. Is it better to keep all enums in one project?
I try to be pragmatic about it, define them where I need them
I tend to have them in Utils project that should not have any dependencies in the solution. But of course if you have enums that are only used in a single project then you should have them in that project.
@@MilanJovanovicTech in enterprise. I think having them in one repository will make it easy to locate them for developers right? I am getting confuse for implementing the best practice.
Thanks
Please Send source code link please!
I share the source code on PAtreon
The implementation would be even cooler with .NET 7 and abstract static methods thingy ;)
I have to check that out, honestly
Great feature, I used similar for value objects from limited range of possibilities. Milan, do You have any tips for vertical slice architecture in Clean Architecture, I am thinking about it... Should I slice it in both core layers separately? How to divide mutual operations for only couple of many slices? I would be really grateful for advices :)
Could you give me a more concrete example? Jimmy Boggard has a great video on the topic
@Milan Jovanović I will watch Jimmy's Boggard video and maybe find my answer there without bothering You. In other cases I will address you with concrete examples. Thanks a million:)
Good job, You can take care of JsonSerialization/Deserialization as a suggestion. (The official implementation supports that). Maybe EFCore default mapping is another concern too.
I usually map only the value for EF Core, although mapping the entire object helps with some queries.
Or use SmartEnum
Excellent library 👌
Not sure why would I need this, especially with int backing field as magic number, but I'm wondering if you tried to use the new 'static abstract' keywords for a card value
How static abstract on interfaces help here?
Pardon, I would not hire you because you seem to have a tendency to overengineer things
I'm in luck then, since I don't need hiring. Phew 🥵
nice, where is code address?
You can get the code on my Patreon, but there are also some examples on my GitHub
Thanks for the video!
Some thoughts about using reflection to get our defined enum values, since we have a very limited amount of instances,, so in this case, in the base enumeration class we can add each instance to the dictionary (we need to use downcast like (TEnum)this but since we have type constraints, we can consider this cast as safe )
and the only problem we still have - static fields is not initialized before we call the actual type, it could be solved with
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TEnum).TypeHandle);
in the base static constructor
I'm not pretending that it's a better solution, just as an alternative to reflection which is not always available...
I think any issue around that can be easily found and fixed
Reflection? Please no!
How do I unsubscribe if I smash the subscribe button.
You can't, you're subscribed forever 🤷♂
No, I think this is really bad. You took a simple enum and turned it into something more like an abstract factory. Stuff like that would be the first thing marked for refactoring in a code review in my company. In this example, keep the actual enum, add an extension method for the enum that creates the credit card classes. Less code, easily understandable by junior engineers and most importantly it requires no inheritance.
Well, if I saw extension methods instead of nice, slick, design, I would mark that for refactoring. There, we're even now 😁
eShopOnContainers is a gold mine, eh? :>
Now do a serialization, deserialization and db storage of those enums. Just to feel real cost of implementation.
Just store the Enumeration value with a value converter. Same storage space as an enum.
@@MilanJovanovicTech That's the point. You've traded 8 lines of code for 2 abstract clases and 3 concrete ones, plus a need for custom converters for what is usually a trivial operation.
Don't get me wrong, looks cool, but in reality it's very niche because of the implementation effort.
@@Lammot It makes sense if you have a lot of logic. This isn't an enum replacement for the simplest use case
@@MilanJovanovicTech can't argue with that. :)
Imho: it's better to use ctor to fill static dictionary, instead of using reflection.
Ctor gets called for every instantiation. In Milans case (with it the Enumeration field being static) the CreateEnumerations Method only gets called once at application startup, where reflection of this size has little to no perf impact.
You would also be in danger of forgetting to add new instances in the ctor if you did it your way.
@@necrokora7688 💯
@@necrokora7688 Would this be on application startup or on first use of a concrete implementation of Enumerator?
Enums are an antipattern. Using data can better describe and is scalable. It doesnt destroy api interfaces like enums do.
Enums are now an antipattern? 😁
Tomorrow writing code will be an antipattern.
@@MilanJovanovicTech i consider them antipattern when writing api code. I dont consider them antipattern for closed applications.
Who, apart from yourself, consider them antipatterns? Can you make a proper case for this point of view?
@@benlewies8828 its only for api code. When you make a change to your api to add/remove/modify an enum and you tell your customers this is an enum. Then they are bound to a specific set of values. If they havent changed their code then they will get an error when parsing. This is extremely bad if you are maintaining systems where you dont want to affect your customers. Also, it doesnt work well if you are versioning unless you have a specific enum for every version which is cumbersome. It is better to just use a text value of the enum.
It seems like you're trying to invent something a bit like a discriminated union.
I'm not inventing anything, Jimmy Bogard made this popular
Nice!, thanks!.🙌
You got it 😎
dont kill enums for class instance garbage, you wasted lots of memory and created performance issue. let ENUM type be just ENUM. NO need to create videos on everything ...
Enums + behavior
Too much gymnastics...
Ok
Or use Ardalis.SmartEnum
Excellent library and includes all the serialisation people are commenting on.
Indeed, great option