So enum Subscriptions goes in tight binding with discounts. What if another aspect of Subscriptions comes to the scene, for example, user privileges (VIP has full access to every resource, FREE - very limited, etc). Would you put it into the enum as well? I personally think that the concern of enum is a strict hard-coded set of values of a particular entity. That's it. Single responsibility. No business logic.
This advice was for BL enums. In that case you can encapsulate all subscription logic in one "enum" class and it's private hierarchy, that allows you to remove all of switch/cases from BL services. In case of another aspect - you should add abstract method in base class, and compiler/intellisence will compel you to realize it in all of inheritors (if it will be a simpe one-liner - it will be a lambda, if not - you can move impementation in separate class/classes, and call it in enums classes)
It reminds me when we have to create a "Maybe" class for handling nullable reference type. This is now directly within C#. My guess is that, one day, we have Java-Like enum if this is a really asked feature. From my perspective, I never have a strong need for this use case (while I'm doing DDD all the way).
I normally don't watch code tutorial videos, but this channel is really different. This is the third or fourth video I've seen in this channel, and I like it because it shows how to use open-source packages (which I really wished to know about before), instead of just abstract code that would fail very often.
It reminds me of the “Discriminated Unions” proposal of C#. It looks promising: Smart enums with attached data, with pattern matching support. I hope that it will be implemented.
As an alternative, you could use base class with children classes and use pattern matching over them. Something like public void DooStuff(Animal animal) { switch (animal) { case Dog dog: dog.Bark(); case Cat cat: cat.Meow(); }
@@denys-p and what if I give them all a method with similar name and parameters for all enum values, but different implementations? So I could get rid of switch and call it from whatever value an enum variable has. Is it possible? Is it effecient?
@@SMT-ks8yp Not sure what you are mean here, but sounds like a Visitor pattern (you can search it and decide if it what you mean or not). It is a slightly different approach. Key differences: 1. With pattern-matching it is easy to add a new method, but hard to add new value into enum/new child class (need to revisit all places where you used it) 2. With Visitor patter it is easy to add a new class ("enum"), but hard to add a new method - you need to revisit all classes ("enums") implementations and add implementation for this method.
@@denys-p Hmmm. From what I know about Visitor, it is done through overloading mehtods, and I'm not sure if you can make exact same parameters for all variants. What I mean is probably more like delegate, but tied to enum so it could switch values along with it.
@@SMT-ks8yp I think it is possible but depends on the specific context. At least, I see no issues with using lambdas/delegates as parameters. Linq works in that way. For example, "Where" receives Func and doesn't care about what happens inside and what variables are captured inside (only in the case of Linq to Objects, Linq to SQL is different and uses Expression instead of Func, despite that syntax looks the same). If it is something that is not inside of nested loops, then probably there are no concerns about performance.
I'm using attributes on enum-values as a way to associate constant data with enum values. Works well with some help of GetAttribute extension methods and a little caching for the reflection part. So i am really hyped for generic attributes
Hm, this might be a good case for source generators. Just write ordinary enums, add some attributes and generate additional helpers and boilerplate code during compilation.
Disagree. Although I dislike 'smart enums' in general, I still gave this a thought. What you would be doing is creating an enum which you would not ever access / use because you want the smart behaviour. It would kinda bloat your code and confuse other developers / lead to inconsistency.
@@mindstyler That could be avoided by declearing the enum blueprint as a private inner enum inside a partial class. The source gen is then hooked to the partial class, effectively replacing the visibility of the original blueprint.
@@twiksify Am I the only one that remembers all the crazy generated code you would see in some classes in 1.1 and early 2.0 days that you literally couldn't touch because one regeneration and it was wiped out?
I never knew about java and its capabilities for enums, but i had a similar requirement for c# and enums. So I did just implement Atrributes. And my C# enums are still enums but with extended capabilities/values
This is the correct way to deal with enums. Use attributes to tag useful information, and extension methods to get the information back from the enum. And with the new code generation features you can easily drop the reflection aspects and generate the extension methods from the enum attributes at compile time.
Although it is a 2 year old video on a topic as simple as Enums, I still holds good value. Learnt something new after watching this video. Thank you for this, Nick!
In C#, make a class with a private constructor and public static members which are instances of that class. Then you have pretty much exactly what you just described from both an implementation and usage point of view. The only difference is you aren't calling it an "enum" in the declaration. The one extra thing you might want to do is override ToString to make it act more like an enum when printing it. You won't be able to switch on it though so maybe that is a deal breaker. You might be able to solve that by adding an implicit cast to an int in the class or something like that. I have not tried it. Maybe this is what that library you are talking about does in the base class. It seems straightforward enough that I would prefer to not include an additional library with my builds just for this feature. Overall, I think that building logic into enums is a bit of an antifeature which can lead to less maintanable code, but that is a whole separate discussion.
One more cool thing in java is that you can use 'lambda' as an interface with one method. And that constructor can be used as 'lambda' that returns a new object. Sometimes I want it in c#
So it sounds like the main use case for this is if the data is always constant like with your planet enum example. Very interesting, I didn't even know you could do that with Java enums. :P lol
Yeah, I love that he says it looks like Rider, when I see Rider, I think it looks like IntelliJ (as did WebStorm and RubyMine I think and every other ide because intelliJ I think came first.)
For those who don't like to inherit to do Discriminated unions or smart enums like this, there is Language-ext package which has Union attribute that does the same thing by generating code on file save. Discriminated union should come soon in c#. It will avoid allocations as well as they will use structs
It adds no coupling. In that context, the enum is the domain concern that owns the values. I know this looks weird and wrong for .NET developers but Java and Kotlin are built with that in mind
@@nickchapsas You got me wrong. I think that embedding values (discount) is a good idea. However by adding inheritance in the mix you're prohibiting extendability. If you'd like to add another value (region) this can't easily be extended via inheritance.
None of the values in this example are static. Anyone that has worked with business/marketing knows that tier names shift and change, and discounts are always being adjusted. Not to mention temporal special tiers at certain times of the year These values will need to be pulled from a data store on creation at the least, and a refresh method most likely as well. I don't know that I see this as a enum type anymore as this is no longer meta, but real data. Perhaps the definition needs to change, but to me enums are still a way to remove magic numbers from the codebase. Still, liked to video and the thought that it invokes.
Wrapping an enum into an object is usually better in medium and bigger projects. It just reduces the number of bugs in the future. If you are using enums, at some point you will be forced to do defensive code against unexpected data (like checking if enum value is not negative) and this defensive code will be repeated across requests. It's good to just wrap it in a value object and make dedicated logic for validation or other stuff.
I have done this many times using custom attributes (akin to the [Descrition] attribute you see often). Make an attribute that takes a nice name and a discount and with a simple extension method, you can retrieve this information for any Subscription value.
Java doesn’t have properties, they haven’t invented it yet :) good one :) On the other hand I wish C# had emums not only like Java, but more like in Rust - there’s the enums masterpiece.
At a company I worked a few years ago we also wanted ENUMs to be smarter... not actually to be able to add logic to them, but mainly make them easier to work with for APIs. If you use an ENUM for DTO classes in an API, and often you end up doing this, the problem is even a minor change to one of these ENUMs will arguably leave you having to create a new API route that utilizes this updated ENUM as it is a breaking change just to update an ENUM. But a solution to this, is actually part of what this SmartENUM does. That it is just a setup with a value and a name for the value. The newest version of the class makes it easier to use the class in code as you can reference all the properties of it, using the static properties that reference what would normally be a locked value in the ENUM. The advantage being, say the consumer of the API is using an older version of the ENUM not having the new... say FreePlus value of the ENUM, it would still be possible to consume as the classes constructor still accepts it to be constructed with a value and name for this otherwise unknown value. Which would make it possible to stick to the current API route and adding a minor, non-breaking, update to it that the consumers can update to when they are ready to do so, but everything will (or at least should) keep working until they do.
The real value is when you can load such mapping between the enum codes and the related properties from the database. There is no big value if you are hard-coding such mapping.
@6:01 "By the way, for context, Java doesn't have properties -- they haven't invented that yet." I love that subtle shade that Nick is throwing at Java! lol =D
The biggest drawback of this approach it's you can't use this in switch. You can always expend on Enum using extension methods, but still it's not solving every issue, the biggest one is - you can't implement interfaces.
Steve Smith has some examples of this in his Github repo. But IMO, the pattern matching experience could be better. I think he is just limited by the language.
Switches are code smells in OO languages. Ideally if you need them, you should only have one of them, in a factory, which then returns an appropriate instance of an object. One switch is fine, but when they start to duplicate they become a maintenance nightmare because any extension or change in behavior must be applied to every switch.
@@steve-ardalis-smith C# is not pure OO language and has a lot of purposes. Switches are great for performance oriented code Also I always thought you use enums when you do know with at least 50% certeinty that you won't need to extend it
Enums are meant to provide a more developer friendly way to assign discrete numeric values to things. They also provide stability when persisting values (I can always add to an enum without breaking any of the persisted data) this approach pretty much breaks all of that. If an enum "value" now basically represents multiple properties you break all previously persisted data if you add another value to your "domain representation" since the number of stored values changes. Either that or you need to have a separate actual enum lying around that is used for persistence, at which point the approach is basically back to having an enumn and a class that does the mapping from enum values to instances of the smart enum so you've gained absolutely nothing. So in conclusion: just because Java and by extension Kotlin didn't understand the difference between a class (which is inheritable or preferably composable to extend it with business logic) and a simple named enumeration doesn't mean it's a smart approach to copy these patterns into a language where the designers were well aware of the difference. I'd actually say that is precisely the reason why this proposal didn't make it into the language specification.
"Enums are meant to provide a more developer friendly way to assign discrete numeric values to things" It's almost never a case. Enums are used to have fixed list of given category (sometimes ordered), the numbers assign to them are usually meaningless. "Either that or you need to have a separate actual enum lying around that is used for persistence" It would add no persistance only more potential bugs to worry about. Readonly properties doesn't change persistance.
@@Krakkk you misunderstood me. Basically my point was any time you need to store your "Smartenum" in a database, in XML, transmit it over a network, basically any time your object with a Smartenum property needs to leave your process's memory, you will need to have a second "notsmartenum" so you can actually store/transmit it without saving out unstable data that will break if you change your Smartenum. (if you just serialize out the Smartenum and you add a third property you run into trouble when you load the Smartenum you stored away when the type only had 2 properties) The more I think about Nick's example in particular I end up with the conclusion that the name and discount should really just be saved out to a config file/database and not be in the source code at all. Hard coding these values is basically a violation of the open closed principle. Your code shouldn't need to change because the discount for a subscription changes, or because management wants an additional fourth subscription tier. And I'd actually argue that this principle doesn't only apply to Nick's example but for the vast majority of cases where you would need this "enum but with additional values" pattern. Your code really should just contain the logic necessary to interpret and operate on the data. (i.e. Read in the subscription name and the discount and the logic to apply the discount to the price) and the data itself should be treated like data and that means stored away and read in as needed. Now you could argue: but I know that objects with a smartEnum never need to leave my process so this is fine. But much of good software engineering is about knowing how to avoid to shoot yourself in the foot down the line. And since we all can't know what future requirements might bring the assumption that at some point the data will indeed need to be transmitted is a good safeguard for the future. Especially since needing to change from Smartenum back to normal enum 2 years down the line will likely be a rather involved and annoying refactoring job.
@@dgschrei Your whole argument assumes that you have to persist all properties, which then breaks backward compatibility after a change. At my previous job we actually only persisted the name of the 'enum' and then through an ORM extension, it got automatically hydrated as the smart enum instance. For our use case, the experience was much better than with a regular enum.
I use record to create DU. public interface IShape { public record Square(int side):IShape; public record Rect(int l,int w):IShape; } IShape c= new IShape.Rect(5,6); //Or assign other c=new IShape.Circle(6); //Matching if(c is IShape.Rect r) WriteLine(r.l*r.w);
Records where my first thought too but I have never thought of putting them in an interface like that. I usually create an abstract record but your way looks a lot better.
Another thing that I've done for years is to add the Description attribute to my enum values. Then I have an extension method that takes an enum and gets the description attribute value or if there isn't one, just ToString()s the enum. It's not real efficient, but it works.
You can also use extension methods. I would not recommend to use this kind of packages because only you know about it and other developers must dig in these package if they want to do some changes
There is an example of this called enumeration classes in eshop on containers. Basically protected constructor being called via public property + rewritten tostring and equality checks.
Does it actually still hold today when we have record types available? Why not just use records for this kind of stuff (which is what I've been doing now) and let the language do its magic?
I did the same thing using Attributes and Extension methods in a few projects. but this still could be a better option because it doesn't use reflection.
Or something like this, as enum values are nothing but fileds anyway. But I ended up not using enums at all when there is a need to attach anything more than a description to them. Simply because such values (subscriptions and discount are a great example) are rarely things that are not pat of a more complex business model: they tend to change some property, or their cardinality. Thus making them not suitable to be hardcoded. It depends, of course, but one should really consider when to expect more from enums as they currently provide. public abstract class EnumLike where T: EnumLike { public int Value { get; private set; }
Nice usage! I have few implementations that I had to do something like, but in my case as The values were fixed, I went to attributes on enums, which I personally found more visual, creating then extension methods do get the values from the attributes by reflection
You can use reflection to get the constant's ordinal (in your example) And extension methods when you don't use fields for enum constant, or when you should use dinamical data, dictionaries, db, etc.... and with it isn`t necessary recreate the enum's behavior in a class (base int, flag, class Enum methods, ordinal, etc.) [enum is struct or data variable && class is a data's object/reference]]
It's a suggestion ... doesn't affect performance, depending on how you useit.... I'd say that the complaint is outdated And your example is a "pointless" code... --but the cuestion is: "pointless" f whom? I'm willing accept ur code expressions, even though with ENUM you have much more performance and better behavior, possibilities, development and so on, without doing anything. When you need extra data you have many ways to get it, and depends your scenary is "pointless" or not
I wonder if we'll ever get the associated type support in c# like in Swift, where each enum case can have its own data type and each can be different. This combined with good pattern matching support makes enum and exhaustive switch cases an amazingly declarative way to program.
Althoght I must say, the price is supprisingly high for not so long block of content (especialy when you are non-hero to hero type of a guy :), as I have been watching your videos here for quite some time, I would concider it a method to show you my support. And also I'm corious as this is your first one.
Thanks for the video. Heard about 'smart' enums at Java few years ago. But think that important is how enums is viewed by Java and C# creators. At C# is just for easier life while working with set of numbers. At Java it is 'regular' class with pre-defined instances. At C# can just create struct if want to group few properties together and define constant fields at struct. Like System.Drawing.Size. It wraps two numbers Width and Height. Plus it has special filed Empty which returns size with zero values. So what is actually added value by your class, except to confuse the enemy (viewer your code)?
I was thinking that most of what his complaint is can be solved with a structure as well. The benefit of a structure is that it is a value type. Only downside I see with a structure vs enum is that the structure cannot be used in a switch (as far as I know or could figure out). However, the SmartEnum class doesn't solve that either - neither structures nor switches can be used in the case statements of a switch. You can get around the switch limitation by using a Dictionary with the struct as a key or the SmartEnum. You can also just use a regular enum with a Dictionary as the key as well. So then the question is there a hit on performance (such as on Dictionary lookups) or memory when using a SmartEnum that is a full class object over a structure that is a value type? The biggest benefit I can see of the SmartEnum is that there is a large amount of boiler plate it handles for you instead of you having to write your own in the struct. Such as you automatically get a readonly Name string property and a Value property. Additionally it handles overriding Equals for you so that comparison is always done on the Value property. Looking at the source code there is some other niceties as well...such as while the default type for Value is int, you can also specify more than just numbers for the Value type. The Value type just has to implement IEquatable and IComparable. To specify type, you do something like "class MySmartEnum : SmartEnum" (or whatever type you want for Value as long as that other type implements the required interfaces). Other niceties seem to be the FromName that does a dictionary lookup based on the string you pass in. This part right here is probably orders of magnitude better than Enum.Parse which takes a heavy hit on performance due to the nature of reflection. So basically, what this boils down to (as far as I can tell): option A) you write structures to handle the stuff an enum can't handle in C#, but for each of these structures you have to write your own Equals and other helper methods, over and over again for each different structure type (cause the structure cannot inherit from another structure). It can implement Interfaces, but you still have to write all the code for each of Interface methods / properties. This falls into the DRY territory. option B) use SmartEnums that provides a bunch of boiler plate functionality for you so you aren't repeating yourself over and over, and then you only write the code that is special and unique to each SmartEnum you write. Some final thoughts: 1) While SmartEnum seems nice, you can't do a [Flags] enum with it (as far I can tell). 2) Because it uses a dictionary for FromValue, the value should be unique. With enums, you can have multiple entries point to the same value - course you might code analysis warnings / errors doing so, but it's still perfectly legal C# code. You can also do something similar with structures. 3) Not sure how they do it (cause I didn't dig to deep into their code), but you can do something like: MySmartEnum e = (MySmartEnum)1;
Hi Nick, It seems when we add more than 4 instances of the "public static readonly Subscription X = new() " that will allocate double the memory. Is there any way to trim that or specify the inital capacity? Thanks!
Great video as always, and great package .... but I learnt something unexpected here, and that is you can represent a double without the leading 0, for example .25 instead of 0.25. Haha, how did I not know that!
How does this work with bitflags? i would also use the singular Subscription instead of the plural to tell that you will only have one enum value instead of multiple like with flags
In kotlin they took this even further. With sealed classes. Basically enums but the values are classes. Pretty neat for API responses and such, you can group together your Success result, Error result, Loading state and what not without having to shove everything into one response object and have half of the object be null, but instead you are able to group completely different objects. Not sure what made them call it a sealed class though, that part is a mistery.
That’s relative to your domain. It doesn’t matter what something “is supposed to be”. What matters is how you can make good use of it so it makes sense to you. If that’s what it is in huge languages like Java and Kotlin, then that’s totally fine on my book.
@@nickchapsas Just because it exists in some other language doesn't mean its automatically a good thing. Can it be useful and handy in the moment? Sure. But that doesn't mean this is the way to go when you need functionality like that. Most of the time when you need something extra to an enum, there is always a chance that you may end up adding more props to it, which then gets real messy real quick. And I certainly wouldn't add a dependency to a random 3rd party nuget to my application for such an insignificant convenience.
Totally understandable opinion. For me it had made sense so many times to have the ability to enrich enums with data that belonged to the enum itself so it has really helped me clean up my code in the past in some cases
@@nickchapsas as much as I like your videos and the idea of this video, but I would have to agree with @GetReady. KISS would my prefer choice. For this approach, do I need to stick a unit test when I creating this enum? The idea seems nice but thing could get messy and misuse other others especially in a bigger team project. Just a thought :)
enums in C# are value type, same as int or struct. And it's actually feature because it won't need unboxing when comparing to some values. It's fast, it consumes less memory. It's a good thing.
This is actually only partially correct. You see, these are static readonly reference types, so they will only be allocated once and reused. They don't cause memory pressure and won't be allocated multiple times. Enums can get boxed and unboxed. These reference types can't. Also, enums are ints are struct. A struct and an in aren't different types. An Int is a struct.
@@nickchapsas I should always read my comments before posting, as I'm always forgetting to be nice first! So let me correct myself. Awesome video and awesome ideas! I actually never felt like enums in C# are lacking something, until recently discovered Kotlin :)
Which is the main difference between this and making a named tuple that would contain all this information? Being able to consume it as a class as well as having it in one place? Just asking to see if I understood what you were trying to convey.
Done this kind of behaviour for many years already with custom attributes and extension methods... with lambdas to indicate which property filter I want to find enum values on... :)
Not sure I need that in the majority of cases when I write code at my every day practice. Though it looks sweet, enums have a bit another purpose in c# (in my perspective - of course). And this is smth like a named representation of a number. And that number in used to “mark” something. I'd like to have a separation of concerns. If I need a number wrapper with logic and label - I have class/structure. Like an encapsulation - is wrapping functions and data they use under a single border(function is called method in this case). I do not think I want to overload such a simple thing as enum with additional logic. Because border between enum and class vanishes.
Doesn't it do compromise memory allocation? Classes go to the Heap and enuns go to stack, right? Perhaps a extension with decorators approach wouldn't be better? Or the reflections could consume all the benefits?
It does but this isn’t garbage collectible memory because you’re dealing with static readonly fields so even tho the memory will be allocated, it’s not memory that can cause GC pressure, which is what we don’t want
i love SmarEnum and using it in my solutions it prevents the boxing on enums, only thing was I wasn't able to do was to set an optional parameter to it when used as a method parameter
An enumeration should represent a constant value. If you want the behaviour (coupling data to "enums"), then you should make a base Subscription class and separate Free, Premium, and VIP classes should inherit this. I think the whole idea of "smart enums" is extending behaviours to a concept that should not be extended past its intended use. OOP solves these problems.
Static readonly fields, are effectively constant values. Sure they are not stack allocated but they are only allocated once and they won’t get GCed so we are fine performance wise
You should not have logic in there that needs to be computed with dependency injection. That would be a sign of it doing too much and maybe showing that this isn’t the right usecase for the library
the course looks pretty good, I really want to take a look to see if its something I could recommend for new-boots-in-the-code. Can your processor handle American Payment Cards?
The important part of using enum - switch and pattern matching - you left aside. What smart enum does is pretty simple to do yourself with records. Regardless of your choice, not sure how you will ensure for a new value on enum to check all switches in your code...
Records are classes unless specified otherwise so if you do it with records and you do it wrong you will get some really nasty allocations. The outcome would be the same but smart enums are more feature rich
yeah... i did that once. sadly there's way more to it. e.g. how would you support JSON serialization especially if you're dependent on 3rd party client generators like NSWAG? i tried so hart configuring that crap through attributes and even through additional processors. same with EFCore, you'd need to register the conversion. i'm really looking forward until that whole workaround stuff becomes native and accepted by all the stuff you'd posisbly want to do with enums ...
I would like to see more Difference betwwen Java and C# videos, as an advanced Java/Spring developer i found interest in C#/NET and want to learn more on the language but do not want to start from Zero, like understanding variable or loop, but I need somerhing straight forward like this video, kindly can you take it into consideration? or make 2 videos 'C# from Java Devs' and 'NET for Spring Devs' ?
I'm curious why you use sealed almost by default in C# so much. Is there a reason you do this? Have you already covered that in another video? I know later on when you do the SubTier constructor examples, that definitely makes sense with sealed, because the main reason for sealed, my understanding is to block derivation, so noone could slip a subclass in that overides that freeTier discount to 1. Maybe you rehearsed this a lot before settling on this recording, and that's why, but I feel like sealed is one of those keywords, that most people, only ever feel the pain from the framework side on, and unless you are in a larger enterprise environment, sealed may not really bring all that much benefit.
So in my opinion sealed should be the default and you should opt in to open something up, just like Kotlin does it. This becomes way more important when you’re working on library code. The reason being that if you open something up then you’re committing to supporting it as a viable option. For most cases it will be fine until you need to change that and then you broke consumers.
@@nickchapsas That is a very interesting Approach. (I don't know Kotlin), But there are lots of Classes I write lately that should never need a subclass. So you in essence propose intentionally closing this addition, knowing that if logic is needed to be added to the class you can then decide is it more functionality for the sealed class itself, or a subclass is the right approach, in which case you bust the seal off the sealed class. I think I can see the merit in this approach. Thanks for sharing.
And now that I think a bit deeper, I've been moving more toward inheriting interfaces the vast majority of the time, rather than class to class for a while now. This would encourage more coding to interface as opposed to implementation to implementation. (is it wrong that I have an itch to go seal a bunch of classes now? ) Love your vids. Keep it up!
That's awkward I actually made this about 10 years ago, the exact same name SmartEnum, same idea very similar implementation with some extras like ability to add enum values dynamically at runtime (database based enums). Sadly it was for a proprietary codebase.
For people saying "It's not what enum suppose to do". So what it suppose to do then? If i can't even have user friendly description of it. There's always some stupid roadblocks when using them.
Exactly. They're almost always *too* primitive and you end up having to add things to them using all manner of hacks. This just happens to be one such (elegant, IMO) hack around language limitations of C#.
What do you mean? In my opinion, it does its job just perfectly well. Let's take his case, if I have that discount enum, I will most likely want to have some methods that can translate that for me- give me a friendly name and discount for it. Why would I want to have constructors and methods inside of my enum? If I want to add properties, constructors, and whatnot I will just create a class. Don't get me wrong it might have its uses, but it seems too complicated and messy for what the purpose of an enum is. Its purpose is simply to allow you to have different states with a DEVELOPER-friendly name, not a user-friendly one. Like flags or just different states of an object. In his example with the subscriptions, I might want that enum to just compare it. Let's say the user object will have a subscription that will have a state, I just want to be able to say subs.State == Subscriptions.xxx instead of having to use subs.State == 1 or subs.State == "Premium" where the first one is undescriptive cuz you won't have any idea what "1" stands for in a few days and the other one is error-prone if you have to keep writing it everywhere Just my opinion, maybe someone will give me a proper explanation of why that may prove useful.
@@vr77323 If all you ever need is an enum, use an enum. But as soon as you start needing more than an enum, instead of hacking around the enum's limitations with extension methods and attributes and other cruft, just use objects. And if you want to just use objects but still get enum-like behavior, use SmartEnum (or your own implementation if you don't want the third-party dependency - it's not a ton of code to do yourself and you can steal the code from SmartEnum if you want. It's MIT licensed).
@@nickchapsas but will that be exhaustive? like compile time save? because in Java if you do switch expression on an enum, add another enum, then the IDE will mark all switch expressions, not handling the new enum as error I tried that with regular enums in C#, but from their nature, the switch expression is never exhaustive for them, unless you add default branch
So what's the benefit of changing an enum as a value type to a class reference type with static class variables to behave as a constant in C#. Seems like some of the benefits of value types are more important such as performance, use of switch statement, etc. Maintainability seems compromised too due to the duplicate code from copying and pasting same class logic (no model or properties changes) to support static var changes. Maybe a struct and/or interface would be just as effective and a more extensible approach.
Static readonly fields are only allocated once, which won’t cause any GC pressure, and they don’t need to be copied like value types so performance is actually fine. You can also totally use them on switch both with pattern matching and with switch expressions.
@Nick Thanks for sharing awesome videos like this, I have a question, shouldn't we keep Enum simple instead of putting business logic in it, I am sure by time enum will grow up and end up with a huge file, and my second point is that enum will also expose those methods to the other class where we don't require those methods. we can handle such scenarios with other techniques so why Enum!, please share if I am missing something?
I use this approach for few years. Write similar extension myself. The same method also even was published on msdn. The biggest disadvantage here is that switch requires testing on constant. So very often you end up implementing additional enum just for switch to work. And in switch itself the code is not as clear as it can be. So you duplicate all this every time as enum or as const string. Pattern matching - yes, but still not as clear as it can be - you need to add those " _ when " everywhere. And pattern matching does not work well with multiline code
Also this approach is very very good when you need to send some COM-port commands with (or without) parameters. You just add some method like byte[] Compose(args) and then use this in syntax like Execute(PortCommand.Read.Compose(arg1, arg2)) It is also nice with logging error messages, like if (result > Error.OK) logger.Error(result.Code, result.Description, result.Recomendation); // or something like logger.Error(Error.ReadFailed.Code, Error.ReadFailed.Description, Error.ReadFailed.Recomendation);
3 года назад
Using it in all our projects. Only problem we have with it is with Swagger which cannot read the static properties, so enums are better in creating and maintaining API documentation
Its a really neat trick. But i think if we have a really big enum, and for each of the values, One result, i think its mutch easyer and Faster to use enum and dictionary and getting the result just by reading the dictionary.
@@nickchapsas You mean performance in the sense of needing to catch attribute information for enums to avoid paying the lookup cost each time you access the attribute data?
So enum Subscriptions goes in tight binding with discounts. What if another aspect of Subscriptions comes to the scene, for example, user privileges (VIP has full access to every resource, FREE - very limited, etc). Would you put it into the enum as well?
I personally think that the concern of enum is a strict hard-coded set of values of a particular entity. That's it. Single responsibility. No business logic.
This advice was for BL enums. In that case you can encapsulate all subscription logic in one "enum" class and it's private hierarchy, that allows you to remove all of switch/cases from BL services. In case of another aspect - you should add abstract method in base class, and compiler/intellisence will compel you to realize it in all of inheritors (if it will be a simpe one-liner - it will be a lambda, if not - you can move impementation in separate class/classes, and call it in enums classes)
It reminds me when we have to create a "Maybe" class for handling nullable reference type. This is now directly within C#.
My guess is that, one day, we have Java-Like enum if this is a really asked feature.
From my perspective, I never have a strong need for this use case (while I'm doing DDD all the way).
I normally don't watch code tutorial videos, but this channel is really different. This is the third or fourth video I've seen in this channel, and I like it because it shows how to use open-source packages (which I really wished to know about before), instead of just abstract code that would fail very often.
It reminds me of the “Discriminated Unions” proposal of C#. It looks promising: Smart enums with attached data, with pattern matching support. I hope that it will be implemented.
As an alternative, you could use base class with children classes and use pattern matching over them.
Something like
public void DooStuff(Animal animal)
{
switch (animal)
{
case Dog dog: dog.Bark();
case Cat cat: cat.Meow();
}
@@denys-p and what if I give them all a method with similar name and parameters for all enum values, but different implementations? So I could get rid of switch and call it from whatever value an enum variable has. Is it possible? Is it effecient?
@@SMT-ks8yp Not sure what you are mean here, but sounds like a Visitor pattern (you can search it and decide if it what you mean or not). It is a slightly different approach. Key differences:
1. With pattern-matching it is easy to add a new method, but hard to add new value into enum/new child class (need to revisit all places where you used it)
2. With Visitor patter it is easy to add a new class ("enum"), but hard to add a new method - you need to revisit all classes ("enums") implementations and add implementation for this method.
@@denys-p Hmmm. From what I know about Visitor, it is done through overloading mehtods, and I'm not sure if you can make exact same parameters for all variants. What I mean is probably more like delegate, but tied to enum so it could switch values along with it.
@@SMT-ks8yp I think it is possible but depends on the specific context. At least, I see no issues with using lambdas/delegates as parameters. Linq works in that way. For example, "Where" receives Func and doesn't care about what happens inside and what variables are captured inside (only in the case of Linq to Objects, Linq to SQL is different and uses Expression instead of Func, despite that syntax looks the same).
If it is something that is not inside of nested loops, then probably there are no concerns about performance.
I did this myself with attributes, extension methods, and reflection. Nice to know this exists now
I'm using attributes on enum-values as a way to associate constant data with enum values. Works well with some help of GetAttribute extension methods and a little caching for the reflection part. So i am really hyped for generic attributes
I already did my smart enums just using base tools of c# it is actually pretty simple to do
Hm, this might be a good case for source generators. Just write ordinary enums, add some attributes and generate additional helpers and boilerplate code during compilation.
That’s actually a really good point!
Disagree. Although I dislike 'smart enums' in general, I still gave this a thought. What you would be doing is creating an enum which you would not ever access / use because you want the smart behaviour. It would kinda bloat your code and confuse other developers / lead to inconsistency.
@@mindstyler That could be avoided by declearing the enum blueprint as a private inner enum inside a partial class. The source gen is then hooked to the partial class, effectively replacing the visibility of the original blueprint.
@@twiksify Am I the only one that remembers all the crazy generated code you would see in some classes in 1.1 and early 2.0 days that you literally couldn't touch because one regeneration and it was wiped out?
@@twiksify that would still leave you with 'unused' code and would break encapsulation. That's a big big code smell imo.
I never knew about java and its capabilities for enums, but i had a similar requirement for c# and enums. So I did just implement Atrributes. And my C# enums are still enums but with extended capabilities/values
This is the correct way to deal with enums. Use attributes to tag useful information, and extension methods to get the information back from the enum. And with the new code generation features you can easily drop the reflection aspects and generate the extension methods from the enum attributes at compile time.
This sounds interesting 👍🏿. May I please have an example, if you don't mind?
Although it is a 2 year old video on a topic as simple as Enums, I still holds good value. Learnt something new after watching this video. Thank you for this, Nick!
In C#, make a class with a private constructor and public static members which are instances of that class. Then you have pretty much exactly what you just described from both an implementation and usage point of view. The only difference is you aren't calling it an "enum" in the declaration. The one extra thing you might want to do is override ToString to make it act more like an enum when printing it. You won't be able to switch on it though so maybe that is a deal breaker. You might be able to solve that by adding an implicit cast to an int in the class or something like that. I have not tried it. Maybe this is what that library you are talking about does in the base class. It seems straightforward enough that I would prefer to not include an additional library with my builds just for this feature. Overall, I think that building logic into enums is a bit of an antifeature which can lead to less maintanable code, but that is a whole separate discussion.
I don't think an implicit cast would work as switch cases require constant expressions.
I've been searching for this for so long!
One more cool thing in java is that you can use 'lambda' as an interface with one method. And that constructor can be used as 'lambda' that returns a new object. Sometimes I want it in c#
Yeah that’s pretty nice as well
So it sounds like the main use case for this is if the data is always constant like with your planet enum example. Very interesting, I didn't even know you could do that with Java enums. :P lol
Speaking of jetbrains, can you do a video on their new IDE called fleet?
Yeah, I love that he says it looks like Rider, when I see Rider, I think it looks like IntelliJ (as did WebStorm and RubyMine I think and every other ide because intelliJ I think came first.)
@@timothywestern6488 Their IDEs are all based on the IntelliJ source.
I’ve been harassing them for access at the NDC Oslo conference so if I am lucky to get in, I will.
For those who don't like to inherit to do Discriminated unions or smart enums like this, there is Language-ext package which has Union attribute that does the same thing by generating code on file save. Discriminated union should come soon in c#. It will avoid allocations as well as they will use structs
This adds coupling between the enum values. I'd much rather use an extension method with a constant lookup table. Composition over inheritance.
It adds no coupling. In that context, the enum is the domain concern that owns the values. I know this looks weird and wrong for .NET developers but Java and Kotlin are built with that in mind
@@nickchapsas You got me wrong. I think that embedding values (discount) is a good idea.
However by adding inheritance in the mix you're prohibiting extendability. If you'd like to add another value (region) this can't easily be extended via inheritance.
Oh I see what you mean! Yeah it definitely comes with its limitations but at that point we are fighting the language itself
@@nickchapsas True indeed, anyhow I'd really like to see more comparison like this. C# has lots to learn from other languages.
Great video as usual!
None of the values in this example are static. Anyone that has worked with business/marketing knows that tier names shift and change, and discounts are always being adjusted. Not to mention temporal special tiers at certain times of the year These values will need to be pulled from a data store on creation at the least, and a refresh method most likely as well.
I don't know that I see this as a enum type anymore as this is no longer meta, but real data. Perhaps the definition needs to change, but to me enums are still a way to remove magic numbers from the codebase.
Still, liked to video and the thought that it invokes.
Wrapping an enum into an object is usually better in medium and bigger projects. It just reduces the number of bugs in the future. If you are using enums, at some point you will be forced to do defensive code against unexpected data (like checking if enum value is not negative) and this defensive code will be repeated across requests. It's good to just wrap it in a value object and make dedicated logic for validation or other stuff.
I have done this many times using custom attributes (akin to the [Descrition] attribute you see often). Make an attribute that takes a nice name and a discount and with a simple extension method, you can retrieve this information for any Subscription value.
Java doesn’t have properties, they haven’t invented it yet :) good one :) On the other hand I wish C# had emums not only like Java, but more like in Rust - there’s the enums masterpiece.
I just love your Greeklish accent, man. Great content too.
At a company I worked a few years ago we also wanted ENUMs to be smarter... not actually to be able to add logic to them, but mainly make them easier to work with for APIs. If you use an ENUM for DTO classes in an API, and often you end up doing this, the problem is even a minor change to one of these ENUMs will arguably leave you having to create a new API route that utilizes this updated ENUM as it is a breaking change just to update an ENUM.
But a solution to this, is actually part of what this SmartENUM does. That it is just a setup with a value and a name for the value. The newest version of the class makes it easier to use the class in code as you can reference all the properties of it, using the static properties that reference what would normally be a locked value in the ENUM. The advantage being, say the consumer of the API is using an older version of the ENUM not having the new... say FreePlus value of the ENUM, it would still be possible to consume as the classes constructor still accepts it to be constructed with a value and name for this otherwise unknown value.
Which would make it possible to stick to the current API route and adding a minor, non-breaking, update to it that the consumers can update to when they are ready to do so, but everything will (or at least should) keep working until they do.
Thank you for the great explanation. I've come from SmartEnum's GitHub page since I didn't understand how it works.
The real value is when you can load such mapping between the enum codes and the related properties from the database. There is no big value if you are hard-coding such mapping.
@6:01 "By the way, for context, Java doesn't have properties -- they haven't invented that yet." I love that subtle shade that Nick is throwing at Java! lol =D
Dude knows what he is talking about. A perfect example is at 6:43.
Every time the patreon grows more, congratulations
I solve your use case with extensions for the enum. I like indeed the C# enums over Java enums, because they are smaller on stack.
The biggest drawback of this approach it's you can't use this in switch. You can always expend on Enum using extension methods, but still it's not solving every issue, the biggest one is - you can't implement interfaces.
You actually can using switch expressions with pattern matching
A yes, i forgot about it.
Steve Smith has some examples of this in his Github repo. But IMO, the pattern matching experience could be better. I think he is just limited by the language.
Switches are code smells in OO languages. Ideally if you need them, you should only have one of them, in a factory, which then returns an appropriate instance of an object. One switch is fine, but when they start to duplicate they become a maintenance nightmare because any extension or change in behavior must be applied to every switch.
@@steve-ardalis-smith C# is not pure OO language and has a lot of purposes. Switches are great for performance oriented code
Also I always thought you use enums when you do know with at least 50% certeinty that you won't need to extend it
Enums are meant to provide a more developer friendly way to assign discrete numeric values to things. They also provide stability when persisting values (I can always add to an enum without breaking any of the persisted data) this approach pretty much breaks all of that. If an enum "value" now basically represents multiple properties you break all previously persisted data if you add another value to your "domain representation" since the number of stored values changes. Either that or you need to have a separate actual enum lying around that is used for persistence, at which point the approach is basically back to having an enumn and a class that does the mapping from enum values to instances of the smart enum so you've gained absolutely nothing.
So in conclusion: just because Java and by extension Kotlin didn't understand the difference between a class (which is inheritable or preferably composable to extend it with business logic) and a simple named enumeration doesn't mean it's a smart approach to copy these patterns into a language where the designers were well aware of the difference.
I'd actually say that is precisely the reason why this proposal didn't make it into the language specification.
Very interesting elaboration @dgschrei thank you.
"Enums are meant to provide a more developer friendly way to assign discrete numeric values to things"
It's almost never a case. Enums are used to have fixed list of given category (sometimes ordered), the numbers assign to them are usually meaningless.
"Either that or you need to have a separate actual enum lying around that is used for persistence"
It would add no persistance only more potential bugs to worry about. Readonly properties doesn't change persistance.
@@Krakkk you misunderstood me. Basically my point was any time you need to store your "Smartenum" in a database, in XML, transmit it over a network, basically any time your object with a Smartenum property needs to leave your process's memory, you will need to have a second "notsmartenum" so you can actually store/transmit it without saving out unstable data that will break if you change your Smartenum. (if you just serialize out the Smartenum and you add a third property you run into trouble when you load the Smartenum you stored away when the type only had 2 properties)
The more I think about Nick's example in particular I end up with the conclusion that the name and discount should really just be saved out to a config file/database and not be in the source code at all. Hard coding these values is basically a violation of the open closed principle. Your code shouldn't need to change because the discount for a subscription changes, or because management wants an additional fourth subscription tier. And I'd actually argue that this principle doesn't only apply to Nick's example but for the vast majority of cases where you would need this "enum but with additional values" pattern.
Your code really should just contain the logic necessary to interpret and operate on the data. (i.e. Read in the subscription name and the discount and the logic to apply the discount to the price) and the data itself should be treated like data and that means stored away and read in as needed.
Now you could argue: but I know that objects with a smartEnum never need to leave my process so this is fine. But much of good software engineering is about knowing how to avoid to shoot yourself in the foot down the line. And since we all can't know what future requirements might bring the assumption that at some point the data will indeed need to be transmitted is a good safeguard for the future. Especially since needing to change from Smartenum back to normal enum 2 years down the line will likely be a rather involved and annoying refactoring job.
@@dgschrei Your whole argument assumes that you have to persist all properties, which then breaks backward compatibility after a change. At my previous job we actually only persisted the name of the 'enum' and then through an ORM extension, it got automatically hydrated as the smart enum instance. For our use case, the experience was much better than with a regular enum.
I use record to create DU.
public interface IShape
{
public record Square(int side):IShape;
public record Rect(int l,int w):IShape;
}
IShape c= new IShape.Rect(5,6);
//Or assign other
c=new IShape.Circle(6);
//Matching
if(c is IShape.Rect r)
WriteLine(r.l*r.w);
Wow this looks natural and awesome.
We could also use record struct for preventing memory allocations for types.
Great.
Records where my first thought too but I have never thought of putting them in an interface like that. I usually create an abstract record but your way looks a lot better.
C#'s extension methods and value converters have always worked well for me in these scenarios. Especially with data bindings.
Yeah but I’m not a fan of wasting memory
I could see the potential for some nasty code here. Honestly, I say leave it the way it is, and if you need to, write an extension.
Indeed, Greek mobile phone numbers do start with 69
I Love this! I think it's amazing, Thanks so much for sharing!
What's the special copy being used at 00:04:30 ?
Another thing that I've done for years is to add the Description attribute to my enum values. Then I have an extension method that takes an enum and gets the description attribute value or if there isn't one, just ToString()s the enum. It's not real efficient, but it works.
Yeah that can really create a performance hit for your app
You can also use extension methods.
I would not recommend to use this kind of packages because only you know about it and other developers must dig in these package if they want to do some changes
Thank you Nick, that was an insightful video.
Keep up the good work my guy, appreciate your effort 👌 💪
There is an example of this called enumeration classes in eshop on containers. Basically protected constructor being called via public property + rewritten tostring and equality checks.
Does it actually still hold today when we have record types available? Why not just use records for this kind of stuff (which is what I've been doing now) and let the language do its magic?
what theme are you using for intellij idea?
Great content as always. I already have a place i want to use this!
I did the same thing using Attributes and Extension methods in a few projects. but this still could be a better option because it doesn't use reflection.
Attributes for Enum is much better for readability.
Just add a source generator for those attributes, no more reflection at runtime! ;)
Or something like this, as enum values are nothing but fileds anyway. But I ended up not using enums at all when there is a need to attach anything more than a description to them. Simply because such values (subscriptions and discount are a great example) are rarely things that are not pat of a more complex business model: they tend to change some property, or their cardinality. Thus making them not suitable to be hardcoded. It depends, of course, but one should really consider when to expect more from enums as they currently provide.
public abstract class EnumLike where T: EnumLike
{
public int Value { get; private set; }
private EnumLike SetValue(int value)
{
Value = value;
return this;
}
public override string ToString()
{
return maps.Value[this];
}
private static Lazy maps = new Lazy(PrepareMap);
private static IDictionary PrepareMap()
=> typeof(T)
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.DeclaringType == typeof(T))
.Select((x, i) => new { x, i })
.ToDictionary(x => (object)((T)x.x.GetValue(null)).SetValue(x.i), x => x.x.Name);
}
public sealed class Subscription: EnumLike
{
public static readonly Subscription Free = new Subscription(0);
public static readonly Subscription Pro = new Subscription(.25);
public static readonly Subscription Vip = new Subscription(.5);
public double Discount { get; }
private Subscription(double discount)
{
Discount = discount;
}
}
void Main()
{
Subscription proSubscription = Subscription.Pro;
Console.WriteLine($"Discount for {proSubscription} subscription is {proSubscription.Discount} ");
}
Сколько костылей ООПшники должны изобрести, прежде чем прийти наконец к размеченным объединениям?
Man, was about to write about ADTs when saw your comment
Love SmartEnums! Use them in all my projects. Jimmy bogard has a good refactoring video using them as well
Nice usage!
I have few implementations that I had to do something like, but in my case as The values were fixed, I went to attributes on enums, which I personally found more visual, creating then extension methods do get the values from the attributes by reflection
I've achieved equivalent "extension" of C# enums using extension methods. Yes, enums work as the 'this' parameter of extension methods!
You can use reflection to get the constant's ordinal (in your example)
And extension methods when you don't use fields for enum constant, or when you should use dinamical data, dictionaries, db, etc.... and with it isn`t necessary recreate the enum's behavior in a class (base int, flag, class Enum methods, ordinal, etc.) [enum is struct or data variable && class is a data's object/reference]]
I prefer to not destroy my app’s performance with pointless reflection usage and tons of unnecessary allocations
It's a suggestion ... doesn't affect performance, depending on how you useit.... I'd say that the complaint is outdated
And your example is a "pointless" code...
--but the cuestion is: "pointless" f whom?
I'm willing accept ur code expressions, even though with ENUM you have much more performance and better behavior, possibilities, development and so on, without doing anything. When you need extra data you have many ways to get it, and depends your scenary is "pointless" or not
I wonder if we'll ever get the associated type support in c# like in Swift, where each enum case can have its own data type and each can be different. This combined with good pattern matching support makes enum and exhaustive switch cases an amazingly declarative way to program.
Thank you. Very helpful!
Can we have in our enum like public enum Test { auto-draft,mec-tickets and so on} ? I need to get these type of values from enum
Althoght I must say, the price is supprisingly high for not so long block of content (especialy when you are non-hero to hero type of a guy :), as I have been watching your videos here for quite some time, I would concider it a method to show you my support. And also I'm corious as this is your first one.
Thanks for the video. Heard about 'smart' enums at Java few years ago. But think that important is how enums is viewed by Java and C# creators. At C# is just for easier life while working with set of numbers. At Java it is 'regular' class with pre-defined instances. At C# can just create struct if want to group few properties together and define constant fields at struct. Like System.Drawing.Size. It wraps two numbers Width and Height. Plus it has special filed Empty which returns size with zero values. So what is actually added value by your class, except to confuse the enemy (viewer your code)?
I was thinking that most of what his complaint is can be solved with a structure as well. The benefit of a structure is that it is a value type. Only downside I see with a structure vs enum is that the structure cannot be used in a switch (as far as I know or could figure out). However, the SmartEnum class doesn't solve that either - neither structures nor switches can be used in the case statements of a switch.
You can get around the switch limitation by using a Dictionary with the struct as a key or the SmartEnum. You can also just use a regular enum with a Dictionary as the key as well. So then the question is there a hit on performance (such as on Dictionary lookups) or memory when using a SmartEnum that is a full class object over a structure that is a value type?
The biggest benefit I can see of the SmartEnum is that there is a large amount of boiler plate it handles for you instead of you having to write your own in the struct. Such as you automatically get a readonly Name string property and a Value property. Additionally it handles overriding Equals for you so that comparison is always done on the Value property.
Looking at the source code there is some other niceties as well...such as while the default type for Value is int, you can also specify more than just numbers for the Value type. The Value type just has to implement IEquatable and IComparable. To specify type, you do something like "class MySmartEnum : SmartEnum" (or whatever type you want for Value as long as that other type implements the required interfaces).
Other niceties seem to be the FromName that does a dictionary lookup based on the string you pass in. This part right here is probably orders of magnitude better than Enum.Parse which takes a heavy hit on performance due to the nature of reflection.
So basically, what this boils down to (as far as I can tell):
option A) you write structures to handle the stuff an enum can't handle in C#, but for each of these structures you have to write your own Equals and other helper methods, over and over again for each different structure type (cause the structure cannot inherit from another structure). It can implement Interfaces, but you still have to write all the code for each of Interface methods / properties. This falls into the DRY territory.
option B) use SmartEnums that provides a bunch of boiler plate functionality for you so you aren't repeating yourself over and over, and then you only write the code that is special and unique to each SmartEnum you write.
Some final thoughts:
1) While SmartEnum seems nice, you can't do a [Flags] enum with it (as far I can tell).
2) Because it uses a dictionary for FromValue, the value should be unique. With enums, you can have multiple entries point to the same value - course you might code analysis warnings / errors doing so, but it's still perfectly legal C# code. You can also do something similar with structures.
3) Not sure how they do it (cause I didn't dig to deep into their code), but you can do something like: MySmartEnum e = (MySmartEnum)1;
Ardalis libraries and templates look really convenient
It's basically a small part of how discrimated unions in F# are represented in IL.
Hi, can you take up the topic of weak references and how to work with them in your next?
Hi Nick,
It seems when we add more than 4 instances of the "public static readonly Subscription X = new() " that will allocate double the memory. Is there any way to trim that or specify the inital capacity? Thanks!
Great video as always, and great package .... but I learnt something unexpected here, and that is you can represent a double without the leading 0, for example .25 instead of 0.25. Haha, how did I not know that!
Ardalis has a lot of cool, little, fancy and helpful staff! I love his Guard and Specification packages
How awesome are Java enums!!!
Enums in Java can even have abstract method that needs to be implemented in a body for each enum constant!
How does this work with bitflags? i would also use the singular Subscription instead of the plural to tell that you will only have one enum value instead of multiple like with flags
In kotlin they took this even further. With sealed classes. Basically enums but the values are classes. Pretty neat for API responses and such, you can group together your Success result, Error result, Loading state and what not without having to shove everything into one response object and have half of the object be null, but instead you are able to group completely different objects. Not sure what made them call it a sealed class though, that part is a mistery.
Kotlin is lovely. I think I need to make a video at some point about why I like Kotlin so much and how it "completes" C# for me
to me it just looks like a misuse of enum. its not what they are supposed to do
That’s relative to your domain. It doesn’t matter what something “is supposed to be”. What matters is how you can make good use of it so it makes sense to you. If that’s what it is in huge languages like Java and Kotlin, then that’s totally fine on my book.
@@nickchapsas Just because it exists in some other language doesn't mean its automatically a good thing. Can it be useful and handy in the moment? Sure. But that doesn't mean this is the way to go when you need functionality like that. Most of the time when you need something extra to an enum, there is always a chance that you may end up adding more props to it, which then gets real messy real quick. And I certainly wouldn't add a dependency to a random 3rd party nuget to my application for such an insignificant convenience.
Totally understandable opinion. For me it had made sense so many times to have the ability to enrich enums with data that belonged to the enum itself so it has really helped me clean up my code in the past in some cases
@@nickchapsas I do understand your argument as well. In any case I learned something new from your video, which is why I'm here after all :)
@@nickchapsas as much as I like your videos and the idea of this video, but I would have to agree with @GetReady. KISS would my prefer choice. For this approach, do I need to stick a unit test when I creating this enum? The idea seems nice but thing could get messy and misuse other others especially in a bigger team project. Just a thought :)
enums in C# are value type, same as int or struct. And it's actually feature because it won't need unboxing when comparing to some values. It's fast, it consumes less memory. It's a good thing.
This is actually only partially correct. You see, these are static readonly reference types, so they will only be allocated once and reused. They don't cause memory pressure and won't be allocated multiple times. Enums can get boxed and unboxed. These reference types can't. Also, enums are ints are struct. A struct and an in aren't different types. An Int is a struct.
@@nickchapsas I should always read my comments before posting, as I'm always forgetting to be nice first! So let me correct myself. Awesome video and awesome ideas! I actually never felt like enums in C# are lacking something, until recently discovered Kotlin :)
Cool Nick ,what if i need to add another type of Subscription that not has nothing to do with discounts .
Which is the main difference between this and making a named tuple that would contain all this information? Being able to consume it as a class as well as having it in one place? Just asking to see if I understood what you were trying to convey.
Done this kind of behaviour for many years already with custom attributes and extension methods... with lambdas to indicate which property filter I want to find enum values on... :)
Not sure I need that in the majority of cases when I write code at my every day practice.
Though it looks sweet, enums have a bit another purpose in c# (in my perspective - of course). And this is smth like a named representation of a number. And that number in used to “mark” something.
I'd like to have a separation of concerns.
If I need a number wrapper with logic and label - I have class/structure.
Like an encapsulation - is wrapping functions and data they use under a single border(function is called method in this case). I do not think I want to overload such a simple thing as enum with additional logic.
Because border between enum and class vanishes.
I'm guessing that comparisons between values using the 'is' keyword would not be possible with these enums?
Doesn't it do compromise memory allocation? Classes go to the Heap and enuns go to stack, right?
Perhaps a extension with decorators approach wouldn't be better? Or the reflections could consume all the benefits?
It does but this isn’t garbage collectible memory because you’re dealing with static readonly fields so even tho the memory will be allocated, it’s not memory that can cause GC pressure, which is what we don’t want
i love SmarEnum and using it in my solutions it prevents the boxing on enums, only thing was I wasn't able to do was to set an optional parameter to it when used as a method parameter
An enumeration should represent a constant value. If you want the behaviour (coupling data to "enums"), then you should make a base Subscription class and separate Free, Premium, and VIP classes should inherit this.
I think the whole idea of "smart enums" is extending behaviours to a concept that should not be extended past its intended use. OOP solves these problems.
Static readonly fields, are effectively constant values. Sure they are not stack allocated but they are only allocated once and they won’t get GCed so we are fine performance wise
OOP *does* solve these problems. That's why SmartEnum uses objects instead of actual primitive enums.
Does this work with dependency injection? Can the constructor get for example a localization service from DIC to localize the friendly name?
You should not have logic in there that needs to be computed with dependency injection. That would be a sign of it doing too much and maybe showing that this isn’t the right usecase for the library
Microsoft rejected a language proposal from the legendary Jon Skeet? Unthinkable!
You honestly make the most interesting and useful videos
the course looks pretty good, I really want to take a look to see if its something I could recommend for new-boots-in-the-code. Can your processor handle American Payment Cards?
Yeah I'm using Stripe so any card (that they support) is supported
The important part of using enum - switch and pattern matching - you left aside. What smart enum does is pretty simple to do yourself with records. Regardless of your choice, not sure how you will ensure for a new value on enum to check all switches in your code...
Records are classes unless specified otherwise so if you do it with records and you do it wrong you will get some really nasty allocations. The outcome would be the same but smart enums are more feature rich
yeah... i did that once. sadly there's way more to it.
e.g. how would you support JSON serialization especially if you're dependent on 3rd party client generators like NSWAG?
i tried so hart configuring that crap through attributes and even through additional processors.
same with EFCore, you'd need to register the conversion.
i'm really looking forward until that whole workaround stuff becomes native and accepted by all the stuff you'd posisbly want to do with enums ...
It supports custom conversions and it also has first party support for things like EF and JSON converters
Java way looks good, I hope C# will support this eventually. But for now I just use custom attributes for pretty much the same.
I would like to see more Difference betwwen Java and C# videos, as an advanced Java/Spring developer i found interest in C#/NET and want to learn more on the language but do not want to start from Zero, like understanding variable or loop, but I need somerhing straight forward like this video, kindly can you take it into consideration? or make 2 videos 'C# from Java Devs' and 'NET for Spring Devs' ?
I'm curious why you use sealed almost by default in C# so much. Is there a reason you do this? Have you already covered that in another video?
I know later on when you do the SubTier constructor examples, that definitely makes sense with sealed, because the main reason for sealed, my understanding is to block derivation, so noone could slip a subclass in that overides that freeTier discount to 1. Maybe you rehearsed this a lot before settling on this recording, and that's why, but I feel like sealed is one of those keywords, that most people, only ever feel the pain from the framework side on, and unless you are in a larger enterprise environment, sealed may not really bring all that much benefit.
So in my opinion sealed should be the default and you should opt in to open something up, just like Kotlin does it. This becomes way more important when you’re working on library code. The reason being that if you open something up then you’re committing to supporting it as a viable option. For most cases it will be fine until you need to change that and then you broke consumers.
@@nickchapsas That is a very interesting Approach. (I don't know Kotlin), But there are lots of Classes I write lately that should never need a subclass. So you in essence propose intentionally closing this addition, knowing that if logic is needed to be added to the class you can then decide is it more functionality for the sealed class itself, or a subclass is the right approach, in which case you bust the seal off the sealed class. I think I can see the merit in this approach. Thanks for sharing.
And now that I think a bit deeper, I've been moving more toward inheriting interfaces the vast majority of the time, rather than class to class for a while now. This would encourage more coding to interface as opposed to implementation to implementation. (is it wrong that I have an itch to go seal a bunch of classes now? ) Love your vids. Keep it up!
Thanks for sharing
That's awkward I actually made this about 10 years ago, the exact same name SmartEnum, same idea very similar implementation with some extras like ability to add enum values dynamically at runtime (database based enums). Sadly it was for a proprietary codebase.
For people saying "It's not what enum suppose to do". So what it suppose to do then? If i can't even have user friendly description of it. There's always some stupid roadblocks when using them.
Exactly. They're almost always *too* primitive and you end up having to add things to them using all manner of hacks. This just happens to be one such (elegant, IMO) hack around language limitations of C#.
What do you mean? In my opinion, it does its job just perfectly well.
Let's take his case, if I have that discount enum, I will most likely want to have some methods that can translate that for me- give me a friendly name and discount for it.
Why would I want to have constructors and methods inside of my enum?
If I want to add properties, constructors, and whatnot I will just create a class.
Don't get me wrong it might have its uses, but it seems too complicated and messy for what the purpose of an enum is. Its purpose is simply to allow you to have different states with a DEVELOPER-friendly name, not a user-friendly one. Like flags or just different states of an object.
In his example with the subscriptions, I might want that enum to just compare it. Let's say the user object will have a subscription that will have a state, I just want to be able to say subs.State == Subscriptions.xxx instead of having to use subs.State == 1 or subs.State == "Premium" where the first one is undescriptive cuz you won't have any idea what "1" stands for in a few days and the other one is error-prone if you have to keep writing it everywhere
Just my opinion, maybe someone will give me a proper explanation of why that may prove useful.
@@vr77323 If all you ever need is an enum, use an enum. But as soon as you start needing more than an enum, instead of hacking around the enum's limitations with extension methods and attributes and other cruft, just use objects. And if you want to just use objects but still get enum-like behavior, use SmartEnum (or your own implementation if you don't want the third-party dependency - it's not a ton of code to do yourself and you can steal the code from SmartEnum if you want. It's MIT licensed).
@@vr77323 I have no idea what are you talking about. In his example you would use it exacly like u use enum subs.State==Subscriptions.xxx
Any chance to have exhaustive switch for SamrtEnums in C#?
Sure, with switch expressions
@@nickchapsas but will that be exhaustive? like compile time save? because in Java if you do switch expression on an enum, add another enum, then the IDE will mark all switch expressions, not handling the new enum as error
I tried that with regular enums in C#, but from their nature, the switch expression is never exhaustive for them, unless you add default branch
So what's the benefit of changing an enum as a value type to a class reference type with static class variables to behave as a constant in C#. Seems like some of the benefits of value types are more important such as performance, use of switch statement, etc. Maintainability seems compromised too due to the duplicate code from copying and pasting same class logic (no model or properties changes) to support static var changes. Maybe a struct and/or interface would be just as effective and a more extensible approach.
Static readonly fields are only allocated once, which won’t cause any GC pressure, and they don’t need to be copied like value types so performance is actually fine. You can also totally use them on switch both with pattern matching and with switch expressions.
@Nick Thanks for sharing awesome videos like this, I have a question, shouldn't we keep Enum simple instead of putting business logic in it, I am sure by time enum will grow up and end up with a huge file, and my second point is that enum will also expose those methods to the other class where we don't require those methods. we can handle such scenarios with other techniques so why Enum!, please share if I am missing something?
I use this approach for few years. Write similar extension myself. The same method also even was published on msdn. The biggest disadvantage here is that switch requires testing on constant. So very often you end up implementing additional enum just for switch to work. And in switch itself the code is not as clear as it can be. So you duplicate all this every time as enum or as const string.
Pattern matching - yes, but still not as clear as it can be - you need to add those " _ when " everywhere. And pattern matching does not work well with multiline code
Also this approach is very very good when you need to send some COM-port commands with (or without) parameters. You just add some method like byte[] Compose(args) and then use this in syntax like Execute(PortCommand.Read.Compose(arg1, arg2))
It is also nice with logging error messages, like
if (result > Error.OK) logger.Error(result.Code, result.Description, result.Recomendation); // or something like logger.Error(Error.ReadFailed.Code, Error.ReadFailed.Description, Error.ReadFailed.Recomendation);
Using it in all our projects.
Only problem we have with it is with Swagger which cannot read the static properties, so enums are better in creating and maintaining API documentation
You can write a custom converter that reads the object and spits the value out
Its a really neat trick. But i think if we have a really big enum, and for each of the values, One result, i think its mutch easyer and Faster to use enum and dictionary and getting the result just by reading the dictionary.
Btw. You can decorate enums with your own attributes. I use a string value attribute to get a friendly name of my enums.
This will use reflection in runtime to get the name leading to really bad performance. For the apps I’m writing that’s a big no no
@@nickchapsas Sounds like I have a nuget package in my future!
Jimmy Bogard wrote about this solution (Enumeration Class) in Los Techies 2012
I think he also has a blog post on it since 2008
Why not use custom enum attributes? Then just add an extension method to fetch the discount value in the attribute.
Deserializers will create new instances instead of reusing the static read-only instances. This will make comparison using == operator fail.
The operator is overloaded and it’s based on the enum value so it will work fine
I was like "what can I learn about enum ?" 3min later "wait switch can return things ?"
Hmm any reason for not using c# attributes to tag your enums with secondary data instead of building up an OOP class structure?
Because of performance
@@nickchapsas You mean performance in the sense of needing to catch attribute information for enums to avoid paying the lookup cost each time you access the attribute data?