How To Create Smart Enums in C# With Rich Behavior

  Рет қаралды 57,962

Milan Jovanović

Milan Jovanović

Күн бұрын

Пікірлер: 237
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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
@trongphan6197
@trongphan6197 2 жыл бұрын
is it simplier ?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
@@trongphan6197 What exactly?
@janedoe6182
@janedoe6182 2 жыл бұрын
10:12 CreateEnumerations() method is overhead. You can push each enum member to the dictionary in Enumeration constructor
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
And initialize it how many times?
@janedoe6182
@janedoe6182 2 жыл бұрын
​@@MilanJovanovicTech Once. Each time you create new enum member constructor add it to dictionary - once for each member
@alfflasymphonyx
@alfflasymphonyx Жыл бұрын
@@janedoe6182 That's what I thought as well.
@MikhailKolobovGamedevForge
@MikhailKolobovGamedevForge 2 жыл бұрын
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.
@vincentvega5104
@vincentvega5104 2 жыл бұрын
He is using classes:)
@MikhailKolobovGamedevForge
@MikhailKolobovGamedevForge 2 жыл бұрын
@@vincentvega5104 Okay... "...use classes which not pretend to be an enums" With all this extra implementing interfaces and useless methods.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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?
@MikhailKolobovGamedevForge
@MikhailKolobovGamedevForge 2 жыл бұрын
@@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)
@JLPA42
@JLPA42 2 жыл бұрын
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).
@mrogalski
@mrogalski 2 жыл бұрын
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 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@mrogalski
@mrogalski 2 жыл бұрын
@@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.
@thomasreasoner6253
@thomasreasoner6253 Жыл бұрын
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.
@mrogalski
@mrogalski Жыл бұрын
@@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
@bahtiyarozdere9303
@bahtiyarozdere9303 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
The example is trivial, so it seems overkill. But think about the concept in broader terms 😁
@oikya5804
@oikya5804 Жыл бұрын
I agree..
@mylesdavies9476
@mylesdavies9476 2 жыл бұрын
Getting a lot of stick for this video in the comments which is uncalled for. I found it interesting, thanks
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I think it's part of expressing your opinions publicly. There will always be people that disagree. 😅
@tomtran6936
@tomtran6936 2 жыл бұрын
Woa. It's quite a good example around many practices and aspects of OOP.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Thanks
@ApacheGamingUK
@ApacheGamingUK 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@errrzarrr
@errrzarrr 2 жыл бұрын
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.
@tapetedepadaria
@tapetedepadaria 3 ай бұрын
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 :)
@MilanJovanovicTech
@MilanJovanovicTech 3 ай бұрын
Go for it! There's also a NuGet package that does this
@svorskemattias
@svorskemattias Жыл бұрын
Looks like strategy pattern + a storable identifier to me. Can you enforce exhaustive pattermatching switch expressions with this?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I'm not sure about that, gotta check
@kasozivincent8685
@kasozivincent8685 Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Awesome, let me take a look 😁
@pureevil379
@pureevil379 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Glad you liked it
@CodeBallast
@CodeBallast 2 жыл бұрын
How about just assigning the Discount property with a getter only from a private constructor? Thereby you don't need three different classes.
@davidwilliss5555
@davidwilliss5555 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@fifty-plus
@fifty-plus Жыл бұрын
We're breaking OCP here. Feels like a ValueObject lib would handle this just as well, or better.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Do we have to be strict about all the principles out there?
@mrsajjad30
@mrsajjad30 2 жыл бұрын
Subscribed. I liked the way you explain things in a easy manner.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Thanks a lot, I have some interesting videos coming in the next few weeks 😁
@mrsajjad30
@mrsajjad30 2 жыл бұрын
@@MilanJovanovicTech looking forward to that.
@harryh212
@harryh212 Жыл бұрын
I prefer to create a custom attribute where I can assign data to my enum members
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Interesting
@k3davis
@k3davis 8 күн бұрын
I've often found enums in c# to be rather limited, probably on purpose. One of the workarounds I've used to add context is also custom attributes. I think this method is actually cleaner, though I'm not sure I would have a ton of use cases for it, seems like a good option to have in the toolbox for a specific use case.
@TheNorthRemember
@TheNorthRemember 2 жыл бұрын
oh god I'm lost this is very complicated solution
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
It makes sense if you need rich behavior in your enums. If all you need is a simple enum, it's certainly too much 😅
@kwwx345
@kwwx345 2 жыл бұрын
@@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.
@alexandrehando
@alexandrehando Жыл бұрын
What would be the difference between a strongly typed enum and an Value Objects?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Intent.
@benlewies8828
@benlewies8828 Жыл бұрын
I can see this being extended to be used in conjunction with caching and persistence, and for use with the strategy pattern.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Strategy pattern is the perfect use case
@aamirali8114
@aamirali8114 Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I don't know, really. Lots of sources. Pluralsight courses. Clean coding book. Refactoring book.
@bjarkeistruppedersen8213
@bjarkeistruppedersen8213 2 жыл бұрын
How would you do this, so it still works with the Flags attribute a normal enum can use?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Probably possible with writing some code to overload the | operator. But I'm not sure how fun that is going be to implement properly 😅
@billacount
@billacount 2 жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Use .NET 6 or .NET 7
@K1llRay64
@K1llRay64 2 ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 2 ай бұрын
You could just return the Id or just the Name? Both are enough to create an instance
@PetrVejchoda
@PetrVejchoda 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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 🤮
@PetrVejchoda
@PetrVejchoda 2 жыл бұрын
@@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.
@МаксимВеснин-и6э
@МаксимВеснин-и6э 11 ай бұрын
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!
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Note that this is the strategy pattern, pretty much. With a way to make it look like an enum. It has its uses.
@moustafaahmed7350
@moustafaahmed7350 18 күн бұрын
One thump like to your videos is not enough.
@MilanJovanovicTech
@MilanJovanovicTech 18 күн бұрын
Much appreciated 😁
@iliyan-kulishev
@iliyan-kulishev 2 жыл бұрын
For most of us, this would look like unnecessary overcomplication. But great trick to have nevertheless. Thanks for the video.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@benlewies8828
@benlewies8828 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Each generic class is a different instance, so the static variable shouldn't be affected
@antonmartyniuk
@antonmartyniuk Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
We're not that lucky 😁
@emadali1906
@emadali1906 2 жыл бұрын
Is this an implementation of the strategy pattern ?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
In a way...
@sajjadarash3295
@sajjadarash3295 Жыл бұрын
That nice thank you for share this❤ But we can to create thatethod create enumeration without reflection in constructore class
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
How?
@sajjadarash3295
@sajjadarash3295 Жыл бұрын
@@MilanJovanovicTech when constructor with two parameter is called add parameters to that Dictionary :D
@chrismsimpson
@chrismsimpson 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
The reflection only runs once per type, so no real performance hit there
@Soraphis91
@Soraphis91 2 жыл бұрын
@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.
@alexmadnix
@alexmadnix 2 жыл бұрын
Thanks for this video!
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
My pleasure!
@5cover
@5cover Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
The only reason it's "over-engineered" is because I'm creating something generic
@thomasreasoner6253
@thomasreasoner6253 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
It's a common idea
@margosdesarian
@margosdesarian 10 ай бұрын
How does this compare with Ardalis SmartEnum approach?
@MilanJovanovicTech
@MilanJovanovicTech 10 ай бұрын
Pretty similar
@ShyamSundar055
@ShyamSundar055 2 жыл бұрын
Well explained 👏 thanks
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
My pleasure 😁
@phw1009
@phw1009 Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
That's a cool idea. Any example out there with a similar implementation?
@ryan-heath
@ryan-heath 2 жыл бұрын
Though the implementation is nice, it is YAGNI for most of the time. There is a reason we have simple enum types. 😉
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
There's also a situation where simple enums aren't enough
@Virdues
@Virdues 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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
@angelldark6426
@angelldark6426 Жыл бұрын
i will never finish my project, you keep giving some new information, i keep updating my project. Please let me finish my project))))
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Don't overengineer it!
@theagemaway
@theagemaway Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Might have to revisit this idea with records :)
@MohammadAhmadi-c1o
@MohammadAhmadi-c1o Жыл бұрын
This way the principle of OCP is violate!
@robby.roboter
@robby.roboter 2 жыл бұрын
Just use Dictionary
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
What will that bring us?
@robby.roboter
@robby.roboter 2 жыл бұрын
@@MilanJovanovicTech less code, same result credit[premium] will give discount for premium
@jfevia
@jfevia 2 жыл бұрын
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.
@JLPA42
@JLPA42 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@jfevia
@jfevia 2 жыл бұрын
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.
@jegtugado3743
@jegtugado3743 2 жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@jorgeutrilla9892
@jorgeutrilla9892 2 жыл бұрын
Nice!, thanks!.🙌
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You got it 😎
@ThiagoBechara
@ThiagoBechara 2 жыл бұрын
That, kids, is a way to implements Abstract Factory Design Pattern. Great video!
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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?
@adambickford8720
@adambickford8720 2 жыл бұрын
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!
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I have to see what Java can do 😁
@nanvlad
@nanvlad 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
How static abstract on interfaces help here?
@waleed-alshinawi
@waleed-alshinawi 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@dxs1971
@dxs1971 2 жыл бұрын
@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
@henrikcarlsen1881
@henrikcarlsen1881 2 жыл бұрын
In my eyes you crossed the river to get water, this being merely a coding exercise. Think the implemtation could be much simpler.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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.
@saeedrezataheri2578
@saeedrezataheri2578 Жыл бұрын
Please Send source code link please!
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I share the source code on PAtreon
@alamir2020
@alamir2020 2 жыл бұрын
In a solution. Is it better to keep all enums in one project?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I try to be pragmatic about it, define them where I need them
@MB-Kajtech
@MB-Kajtech 2 жыл бұрын
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.
@alamir2020
@alamir2020 2 жыл бұрын
@@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
@AboutCleanCode
@AboutCleanCode 2 жыл бұрын
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: kzbin.info/www/bejne/f6C4pKx7rryKZs0
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
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 🤔
@AboutCleanCode
@AboutCleanCode 2 жыл бұрын
@@MilanJovanovicTech sounds like "Smart Enums - Part 2" ;-)
@mkwpaul
@mkwpaul 2 жыл бұрын
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
@MilanJovanovicTech 2 жыл бұрын
You must not be a fan of DDD also 🤔
@mkwpaul
@mkwpaul 2 жыл бұрын
​@@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#.
@gabdemello
@gabdemello Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
Parse on controller level?
@gabdemello
@gabdemello Жыл бұрын
@@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!
@rasimismatulin1400
@rasimismatulin1400 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
4. EF Value Converter is what I use, works great
@mjunior771
@mjunior771 Жыл бұрын
Maybe a better solution would be to use the abstract factory design pattern?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
This is one way to implement something similar. I see it more as the strategy pattern
@jadenrogers3133
@jadenrogers3133 Жыл бұрын
Steve Smith's SmartEnum nuget package does this.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Cool!
@1Eagler
@1Eagler Жыл бұрын
Going from NY to Boston through LA.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Did you make it?
@richardhaughton9633
@richardhaughton9633 2 жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
If all the data is static, it can be enough to store the Value integer with value converters
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Can be a separate video 👌
@hamidhos8101
@hamidhos8101 2 жыл бұрын
nice, where is code address?
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You can get the code on my Patreon, but there are also some examples on my GitHub
@eyupcalis9275
@eyupcalis9275 2 жыл бұрын
Nice video. i will use this enumeration class. Thank you 😊
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Thanks, glad you liked it!
@perfectionbox
@perfectionbox 2 жыл бұрын
Hmmm so it's a Factory pattern that returns subclasses given an enum value. People already do that.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Who said they didn't? 🤔
@sawek111
@sawek111 2 жыл бұрын
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 :)
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Could you give me a more concrete example? Jimmy Boggard has a great video on the topic
@sawek111
@sawek111 2 жыл бұрын
​ @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:)
@SeCluDred
@SeCluDred 2 жыл бұрын
I smushed subscribe button so hard that now it is stuck inside UI :D
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Is that why I got 100 subscribers out of nowhere? You really smashed it good 😂
@RobertPaulsim
@RobertPaulsim 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
What is horror code here exactly? 🤔
@reagang8038
@reagang8038 2 жыл бұрын
Excellent video. Thanks for sharing this
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Thanks, glad you liked it! 😁
@nghianguyen170192
@nghianguyen170192 Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Fair enough, I'm here to show ideas. You decide what works and doesn't work for you :)
@craigmunday3707
@craigmunday3707 9 ай бұрын
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.
@errrzarrr
@errrzarrr 2 жыл бұрын
-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)
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I guess I did -1 and -2 combined
@manapotion1594
@manapotion1594 2 жыл бұрын
Pardon, I would not hire you because you seem to have a tendency to overengineer things
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I'm in luck then, since I don't need hiring. Phew 🥵
@DavidSmith-ef4eh
@DavidSmith-ef4eh 25 күн бұрын
php already has that. never considered how good we have it with PHP enums.
@MilanJovanovicTech
@MilanJovanovicTech 24 күн бұрын
Yep, Java also but not in C#
@DavidSmith-ef4eh
@DavidSmith-ef4eh 24 күн бұрын
@@MilanJovanovicTech if I had a magical wand to convert our project from PHP to c#, I would still do it though.. even with the worse enum solution,.
@MarcusKaseder
@MarcusKaseder 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
That's a very interesting extensibility option, it hasn't crossed my mind
@rafaelscheffer1262
@rafaelscheffer1262 2 жыл бұрын
great video, thanks for sharing
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Thanks, Rafael. Glad you liked it
@majormartintibor
@majormartintibor 2 жыл бұрын
Nice video, thanks for this.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You're among the few that enjoyed it. It seems I triggered a lot of people 😂
@majormartintibor
@majormartintibor 2 жыл бұрын
@@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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
@@majormartintibor Just weigh it carefully against the increase in complexity
@amirsolhi3150
@amirsolhi3150 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I usually map only the value for EF Core, although mapping the entire object helps with some queries.
@fernandocalmet
@fernandocalmet 2 жыл бұрын
Very good strategy to have Enums with behavior. This goes perfectly for DDD architectures. Thanks for sharing Milan :)
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I've used in DDD typed applications mostly
@porcinetdu6944
@porcinetdu6944 2 жыл бұрын
That is a massive like
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Glad you liked it 😁
@dcp15121980
@dcp15121980 2 жыл бұрын
How do I unsubscribe if I smash the subscribe button.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
You can't, you're subscribed forever 🤷‍♂
@inayelle
@inayelle 2 жыл бұрын
The implementation would be even cooler with .NET 7 and abstract static methods thingy ;)
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I have to check that out, honestly
@mihaikanyaro3460
@mihaikanyaro3460 2 жыл бұрын
Or use SmartEnum
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Excellent library 👌
@Lammot
@Lammot 2 жыл бұрын
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
@MilanJovanovicTech 2 жыл бұрын
Just store the Enumeration value with a value converter. Same storage space as an enum.
@Lammot
@Lammot 2 жыл бұрын
@@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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
@@Lammot It makes sense if you have a lot of logic. This isn't an enum replacement for the simplest use case
@Lammot
@Lammot 2 жыл бұрын
@@MilanJovanovicTech can't argue with that. :)
@nikitafrolov7668
@nikitafrolov7668 2 жыл бұрын
Imho: it's better to use ctor to fill static dictionary, instead of using reflection.
@necrokora7688
@necrokora7688 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
@@necrokora7688 💯
@benlewies8828
@benlewies8828 Жыл бұрын
@@necrokora7688 Would this be on application startup or on first use of a concrete implementation of Enumerator?
@HagobSaldadianSmeik
@HagobSaldadianSmeik 2 жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Well, if I saw extension methods instead of nice, slick, design, I would mark that for refactoring. There, we're even now 😁
@T___Brown
@T___Brown 2 жыл бұрын
Enums are an antipattern. Using data can better describe and is scalable. It doesnt destroy api interfaces like enums do.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Enums are now an antipattern? 😁 Tomorrow writing code will be an antipattern.
@T___Brown
@T___Brown 2 жыл бұрын
@@MilanJovanovicTech i consider them antipattern when writing api code. I dont consider them antipattern for closed applications.
@benlewies8828
@benlewies8828 Жыл бұрын
Who, apart from yourself, consider them antipatterns? Can you make a proper case for this point of view?
@T___Brown
@T___Brown Жыл бұрын
@@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.
@dakotapearl0
@dakotapearl0 2 жыл бұрын
It seems like you're trying to invent something a bit like a discriminated union.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I'm not inventing anything, Jimmy Bogard made this popular
@fenkusingh
@fenkusingh 7 ай бұрын
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 ...
@MilanJovanovicTech
@MilanJovanovicTech 7 ай бұрын
Enums + behavior
@usmanfarooq_dev
@usmanfarooq_dev Жыл бұрын
Too much gymnastics...
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Ok
@serhiyskaletsky9095
@serhiyskaletsky9095 2 жыл бұрын
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...
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
I think any issue around that can be easily found and fixed
@errrzarrr
@errrzarrr 2 жыл бұрын
Reflection? Please no!
@chrix_app
@chrix_app 2 жыл бұрын
Or use Ardalis.SmartEnum
@allannielsen4752
@allannielsen4752 2 жыл бұрын
Excellent library and includes all the serialisation people are commenting on.
@MilanJovanovicTech
@MilanJovanovicTech 2 жыл бұрын
Indeed, great option
Functional Programming With C# Using Railway-Oriented Programming
19:15
Milan Jovanović
Рет қаралды 26 М.
Exceptions Are Extremely Expensive… Do This Instead
17:15
Milan Jovanović
Рет қаралды 51 М.
She made herself an ear of corn from his marmalade candies🌽🌽🌽
00:38
Valja & Maxim Family
Рет қаралды 18 МЛН
Enceinte et en Bazard: Les Chroniques du Nettoyage ! 🚽✨
00:21
Two More French
Рет қаралды 42 МЛН
EF Core Performance Optimization Challenge | 233x FASTER
14:42
Milan Jovanović
Рет қаралды 71 М.
How to write "smarter" enums in C#
12:56
Nick Chapsas
Рет қаралды 135 М.
How Strongly Typed IDs Can Make Your Code More Expressive | DDD
12:02
Milan Jovanović
Рет қаралды 36 М.
How To Create Generics in C#, Including New Features
38:51
IAmTimCorey
Рет қаралды 54 М.
C++ Super Optimization: 1000X Faster
15:33
Dave's Garage
Рет қаралды 334 М.
How To Use The Specification Design Pattern With EF Core 6
19:13
Milan Jovanović
Рет қаралды 42 М.
how Google writes gorgeous C++
7:40
Low Level
Рет қаралды 994 М.
The Right Way To Build Async APIs in ASP.NET Core
20:12
Milan Jovanović
Рет қаралды 27 М.
Get Rid of Exceptions in Your Code With the Result Pattern
13:06
Milan Jovanović
Рет қаралды 58 М.
Don't Use Polly in .NET Directly. Use this instead!
14:58
Nick Chapsas
Рет қаралды 77 М.