How can I compare a System.Enum
to an enum
without boxing? For example, how can I make the following code work without boxing the enum
?
enum Color
{
Red,
Green,
Blue
}
...
System.Enum myEnum = GetEnum(); // Returns a System.Enum.
// May be a Color, may be some other enum type.
...
if (myEnum == Color.Red) // ERROR!
{
DoSomething();
}
To be specific, the intent here is not to compare the underlying values. In this case, the underlying values are not meant to matter. Instead, if two Enums have the same underlying value, they should not be considered equal if they are two different kinds of enums:
enum Fruit
{
Apple = 0,
Banana = 1,
Orange = 2
}
enum Vegetable
{
Tomato = 0,
Carrot = 1,
Celery = 2
}
myEnum = Vegetable.Tomato;
if (myEnum != Fruit.Apple) // ERROR!
{
// Code should reach this point
// even though they're the same underlying int values
Log("Works!");
}
This is basically the same functionality as Enum.Equals(Object)
. Unfortunately Equals()
requires boxing the enum, which in our case would be a naughty thing to do.
Is there a nice way to compare two arbitrary enums without boxing or otherwise creating a bunch of overhead?
Thanks for any help!
This can be done with a generic implementation like so:
private static bool Equals<TEnum>(Enum first, TEnum second)
where TEnum : struct
{
var asEnumType = first as TEnum?;
return asEnumType != null && EqualityComparer<TEnum>.Default.Equals(asEnumType.Value, second);
}
The only heap allocated memory will be the lazy instantiation for each EqualityComparer<TEnum>.Default
value, however this will only happen once for each type of enum.
I'd be careful about worrying about GCs to this level unless you have a really specific need to ensure low latency. This doesn't mean "my web sever gets a lot of hits", it means things like sound/video playback, games, or low latency trading applications. If it's just "my app has to not be slow" then avoiding the GC completely is overkill. If you're avoiding boxing to avoid one allocation, you need to be avoiding every other allocation as well, which will include logging, creating closures, any string concatenation and of course any use of the new keyword for a reference type.
However, if you are genuinely in a scenario where you must avoid the GC and this is the last or worst of your allocation problems, then it may be worth looking into. Be sure to have a read through the documentation on GC Latency Modes - if you only need low latency for specific periods, then you can change the behaviour of the GC to suit you without changing all your code to avoid allocating memory.
So looking at the enum boxing issue. As others have already said, the value is already boxed inside the System.Enum
object. You said in a comment that this is fine because this value is defined once for the application lifetime. In that case, I would consider defining it not as an enum, but as static class values. See this question for more details on the general approach.
If you take this approach, then you have a single object created for each possible enum value for the entire application lifetime. You're then free to use reference comparison or any other comparison you want and you won't have to deal with boxing.
So for your example, you could have something like
class Color
{
public static Color Red { get; } = new Color();
public static Color Green { get; } = new Color();
private Color()
{
}
}
Then any comparisons between enum values are by definition between two objects, so no boxing/unboxing occurs at comparison time. And now you definitely have a clear distinction between different enum types - Fruit.Apple and Vegetable.Tomato are never going to compare equal.
You need to cast which has almost not impact on performance. (even if you were boxing... which you wouldn't be... it would have very little impact.)
If you are worried then use a generic method.
TEnum GetEnum<TEnum>() where TEnum : struct
Then you would get back the back type you are expecting and no casting or boxing would take place.
So you have:
System.Enum myEnum = ...;
and you know myEnum
is already some boxed enum value. You want to compare that to Color.Red
without creating another box with Color.Red
in it. You could do that with:
if (myEnum is Color && (Color)myEnum == Color.Red)
{
...
}
The is
keyword will re-use the existing box. The conversion from System.Enum
to Color
is an unboxing conversion (not sure if the type check will not be performed twice but we are really really micro-optimizing here). The ==
here will do a simple integer comparison on the two numerical values (for example two 32-bit integers, depending on the underlying integral type of Color
) residing on the call stack.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With