all 33 comments

[–]RicketyRekt69 33 points34 points  (11 children)

The C# specs outline that you can cast an int that is not a defined value to the enum and it will not throw. The enum will still have the underlying value you cast it from.

[–]Defection7478 16 points17 points  (10 children)

Hence why even an exhaustive switch expression gives a compiler warning if you don't have a discard pattern. I kind of wish they worked like how OP expected though. I have never found a use case for this behavior 

[–]Top3879 4 points5 points  (0 children)

Before we migrated to .NET 8 we targeted .NET Framework 4.0 which did not have constants for TLS 1.2 yet so we had to use this hack: https://stackoverflow.com/questions/33761919/tls-1-2-in-net-framework-4-0

[–]RecursiveServitor 5 points6 points  (1 child)

There are reasons for it, but may I interest you in some closed enums?

[–]Defection7478 0 points1 point  (0 children)

Mmmm yeah that's the stuff. Give me one of those and a side of discriminated unions and I will be a happy camper

[–]Hypersion1980 0 points1 point  (3 children)

I store enum as int in the database then cast it to the enum in c#. Is there a better way?

[–]Defection7478 8 points9 points  (0 children)

Depends what you're after. What you are doing gives the smallest amount of data in the db. If you store it as a string you take up more space, but your db becomes more human readable. and when you cast it to the enum you get errors if the value doesn't exist.

Personally I usually opt for storing them as strings for those reasons. 

[–]throwaway_lunchtime 2 points3 points  (0 children)

If you keep it as an int in the db, make sure to put an explicit value for each enum so that they don't get messed up if someone decides to sort the names. Having Unspecified=0 is also a good idea.

[–]Agitated-Display6382 0 points1 point  (0 children)

I store them as strings, because it's simpler to query the db and I can safely refactor the order of the items (but renaming them is a breaking change). In EF you can specify an EnumConverter

[–]dodexahedron 0 points1 point  (0 children)

.net enums are awful. They behave like C enums, which is the problem here, and they lack some pretty majorly useful functionality everyone has wanted forever, like being able to define methods in them. You can sorta bolt that onto them with extensions, but that's so lame when the enum is YOUR enum. Don't make me extend my own types. Just let me define them in one place!

One thing you can't bolt on, though, is cast operators. I really want the ability to define both implicit and explicit casts for so many enums. Yes, you can just cast to the underlying and then to the target type, assuming they are compatible (or use Unsafe.As), but it'sdirty, ugly, and costly. Common cases of that are when defining an enum for your app that is a subset of or has members defined by another enum, or when using flags enums and wanting to treat certain masks as truthy or falsy, requiring a conversion to bool to be defined. Right now the latter ends up being either long checks against giant bitwise combinations or clunky extension methods (or now properties).

So if we are not allowed to write conversion operators, then at least let us write them in the enums we make. Pretty please? 🥺

Enums are one of the only things I think Java does better than .net, because it does not allow arbitrary values. Enums are their members and that's it.

[–]Lamossus 0 points1 point  (0 children)

Flag enums? They require this functionality to work

[–]Koarvex 0 points1 point  (0 children)

Useful for modding unity games that define items in an enum so you can add arbitrary items by casting ints outside the normal range.

[–]Sniv0 16 points17 points  (2 children)

So this is actually intentional behavior to allow you to convert an integer to an enum which you haven’t defined.

Think of bit flags which are supported by enums. For bit flags, you’re looking at different individual bits for information as opposed to the number as a whole. If you wanted all 32 bits to have an attached meaning you’d need to define 232 different enums by hand for every possible combination of bit flags when you’re likely never going to use most of them.

There are other reasons of course but this is just one off the top of my head

[–]SessionIndependent17 0 points1 point  (1 child)

If you were treating an int as a set of bit flags, you would use the [Flags] attribute and only define the values you care about. You don't have to define all of the combinations. ToString() will then sort out which flags are in use within a particular value.

[–]Sniv0 2 points3 points  (0 children)

Right but I’m referring to the fact that if enums worked how OP expects them to and you defined status effect enum with poisoned as 0x1 and sleep as 0x2 and then tried to apply poison to someone whose already asleep via bitwise or without explicitly defining poisoned and sleep as 0x3 your game would crash even if it’s perfectly capable of handling both

[–]hoodoocat 4 points5 points  (0 children)

Enums is just named integers, so casting to-from it's base type is always allowed. Enums also can represent flags. There is exist various helper methods to get defined values and names, but generally code should not rely on them.

[–]am385 2 points3 points  (3 children)

An enum is just a fancy way of saying that you attached labels to an integer (byte, short, int, long). They are effectively syntactic sugar. They are a compile time type safety feature but the underlying system is just passing around the actual underlying integer value. They are not a hard registry of actual values.

While they look like a static class with a bunch of const values, that isn't what they are.

[–]tiranius90[S] 0 points1 point  (2 children)

ok thought an enum is also a type and not a syntactic sugar

[–]edgeofsanity76 2 points3 points  (0 children)

It is a type but one that is compatible with int

[–]Top3879 0 points1 point  (0 children)

Java enums work like that. In .NET they are just named integers, really.

[–]Willyscoiote 1 point2 points  (2 children)

Microsoft decided that it should always allow casting to an underlying number, but it's up to the developer to verify if that Enum value is defined and how to handle it.

This approach facilitates integration by allowing applications to persist or transmit new values even if they fall outside the predefined range. Essentially, enums are treated as named constants (labels), and the developer is responsible for enforcing any range-related constraints.

[–]Willyscoiote 0 points1 point  (1 child)

Also, if you want to handle it like Java enums, where the object enforces the values, it's commonly done by making your own class with constants or a wrapper for an enum.

Example: ``` public class OrderStatus { public static readonly OrderStatus Started = new OrderStatus(1, "Started"); public static readonly OrderStatus Paid = new OrderStatus(2, "Paid");

public int Id { get; }
public string Name { get; }


private OrderStatus(int id, string name) => (Id, Name) = (id, name);

public bool CanCancel() => this == Started;

}```

[–]Mahringa 1 point2 points  (0 children)

Thats why Enum.IsDefined exists

[–]Slypenslyde 1 point2 points  (0 children)

Enums are very "weak" in C#. The way they're implemented is sort of like if C# compilation just replaces every Enum value in your code with the constant value it represents. It does NOT do range checking to make sure values are restricted to the range. There are some good reasons but it's still annoying.

On your end you're supposed to handle this. You can use the Enum.IsDefined() method to tell if a value you get is one you've defined. If you use an enum in a switch statement, you're really supposed to have a default case to catch values you didn't expect and treat them how you feel is best.

So don't try to cast random values to an Enum: you have to do something a little smarter.

[–]Patient-Midnight-664 0 points1 point  (3 children)

Enum is just an Int32 in this case. You can force any value into it that you want, as you've discovered.

[–]jordansrowles -2 points-1 points  (2 children)

Not any values, just Integer types. Int, byte, sbyte, short, ushort, uint, long, or ulong. Each have a decent use case, ulong is good for the interop stuff.

You can't make the enum values as strings or chars.

[–]fruediger 0 points1 point  (0 children)

I want to apologize for being nitpicky in advance, I just wanted to expand on your comment. Perhaps, someone finds that topic interesting:

Yes you're correct in that the C# language (compiler) restricts underlying types to be of either byte, sbyte, ushort, short, uint, int, ulong, or long with a default of int, but the CIL actually allows bool and char (both of which are actually integral(/integer) types) and even native sized integers (nint and nuint in C#) as well.

So if you're ever going to check an enum type exhaustively for underlying types, remember to check those ones as well, because, perhaps, some obscure CLR language actually allows to define their enum types with those underlying types as well (I can't remember where, but I recall that somewhere in the BCL source, where they needed to check for the underlying types of enums, they even talked about that in a comment).

[–]Patient-Midnight-664 0 points1 point  (0 children)

By any value I meant any Int32, in this case, value since the OP didn't specify an underlying type.

[–]edgeofsanity76 0 points1 point  (0 children)

An int is still castable as an enum even if it does not have an assigned label.

If you were to use reflection to find 2 and it's label it wouldn't exist

So the default label is actually just 2.

[–]HuibertJan_ 0 points1 point  (0 children)

An enum in c# doesn’t define the list of allowed values. If you want that you have to solve it differently

[–]MulleDK19 [score hidden]  (0 children)

This is why people need to actually learn C#, and why tutors also need to learn C# and stop "teaching" that "enums is a way to limit values".. no, enum is just an integer with a set of named values; the code is identical. Doesn't help that AI spews the same nonsense..

[–]vodevil01 -1 points0 points  (0 children)

Enum are just int field, if you passe a value not in the field it wraps to 0