New year, new me… Just kidding, no running or yoga plans, but I decided to have a (temporary) break with ‘C# attributes you should know’ series and start with a brand new one – C# facts that somehow surprised me. For the first article, I will show you how (friendly looking, always helpful, good guy) enum can become an asshole. An asshole who will steal your precious time!
One enum value passed to a method, other value received! What’s goin’ on?
My colleague asked me recently to have a look with ‘a fresh eye’ at some code. The situation was strange… We have a method that takes our enum as a parameter and processes it somehow. We passed as a parameter OurEnumType.ValueY but in the method’s body, parameter had value OurEnumType.ValueX. Strange, isn’t it?
Tl;dr;
To fully understand the situation, have a look at the method below. It just reads the enum description straight from the [Description] attribute. No magic included, so don’t over analyze it ?.
public static void ShowEnumLabel(Animals val) { Type type = val.GetType(); string name = Enum.GetName(type, val); if (name != null) { var field = type.GetField(name); if (field != null) { if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attr) { Console.WriteLine(attr.Description); return; } } } Console.WriteLine("No matching label!"); }
And this is how we use this method:
ShowEnumLabel(Animals.Cat);
The expected result was Description attribute for Animals .Cat shown on the console, right?
Unfortunately, in the console window, we can see Animals.Dog description (!!!).
If you still don’t have any suspicion what’s going on, let me show you some more code…
public enum Animals { [Description("Tweet tweet! Bird here!")] Bird = 1, [Description("Bark bark! Guess what? Dog here!")] Dog = 2, //... More and more values here... [Description("Meow! Cat here!")] Cat = 2, [Description("Whatzzzzzzz up! Mouse here!")] Mouse = 100, }
Do you see, where is the catch? ?
Two enum values with the same value!
Exactly! Dog and Cat have both numeric value equal 2.
Why does this code even compile!? Visual Studio should return some nice error and stop the author from creating this nonsense! But it didn’t! There are no errors! Even no warnings! Or messages. Absolutely nothing, the code is clean, according to the VS.
But is it even legal? Actually, yes. The question should be ‘Why is it permitted?’.
Well, we have to go deep down, on the C# bottom, when strange creatures swim…
…or just open Visual Studio’s Object Browser tab in our project and check what our Animal enum really is. Because things never are what they seem to be ?.
Wait a second… Is enum a class?
Not exactly. Neither is it a struct. So, what is it? Right now, enum appears to me as something between a struct and a class. We could use a struct with public constant members to mimic an enum. In our example, it would look like this:
public struct AnimalStruct { public const int Bird = 1; public const int Dog = 2; public const int Cat = 2; public const int Mouse = 4; } public static void ShowEnumLabel(int val) { //… }
Calling ShowEnumLabel method would be similar to the previous version:
ShowEnumLabel(AnimalStruct.Cat);
But… A struct cannot inherit a class, right? And in Object Browser we see, our Animal type inherits System.Enum.
Sooo, maybe enum is a class? Nope! After all, it’s a value type (if you want to pass null to our method ShowEnumLabel, val parameter has to be nullable – Animal? val).
So, what the heck enum is?
Well, nobody knows ?. Probably even in Microsoft they forgot it. And when two of Microsoft programmers meet at the corridor, they don’t talk about it, hoping nobody will ever ask what enum is.
But it’s safer to think about enum as something between a class deriving from System.Enum and a struct with public constant values. You may wonder (of course you are!) why I wrote ‘It is safer’… Because when we treat our friendly looking, old friend enum as a struct (or a class), we remember, this nasty creature can have members with duplicated values. And this way we avoid the situation when the below equitation returns true:
Animals.Cat == Animals.Dog
If this (much too long, I know!) article seems to be just a boring anegdote, think for the moment of the below mistake, that could happen to anybody:
public enum BankOperationType { Deposit = 1, Withdrawal = 1 }
And right now, the only way to avoid this situation, IMO, is to stop setting numeric values to enum members. But sometimes you really need them :(.
Anyway, have a nice coding! And never ever trust enum! 😉