35

It recently occured to me that the following (sample) enumeration...

enum Color
{
    Red,
    Green,
    Yellow,
    Blue
}

... could be replaced with a seemingly more type-safe class:

class Color
{
    private Color() { }

    public static readonly Color   Red      = new Color();
    public static readonly Color   Green    = new Color();
    public static readonly Color   Yellow   = new Color();
    public static readonly Color   Blue     = new Color();
}

With "type-safe", I mean that the following statement would work if Color was an enum, but not if Color were the above class:

var nonsenseColor = (Color)17;    // works if Color is an enum

Two questions:

1) Is there a widely accepted name to this pattern (replacing an enum with a type-safe class)?

2) In which cases should one use enums, and when would a class be more appropriate?

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 2
    I'm not sure that there is a named pattern for replacing an enum with a class, but personally I don't see the additional value. The Enum is simple, and efficient for cases such as the above. – KP. Jan 22 '10 at 19:07
  • 2
    Enums in Java are similar to what you're trying to achieve. – empi Jan 22 '10 at 19:16
  • 1
    One advantage of the enum-as-class approach is that you can't assign out-of-range values, as I demonstrate. I'm not sure if there *are* more advantages apart from that, or if there are even disadvantages -- that's what I'd like to find out with this question. – stakx - no longer contributing Jan 22 '10 at 19:19
  • 2
    Enums are very cheap, they are value types. – Hans Passant Jan 22 '10 at 19:54
  • 1
    @stakx: You might also be able to achieve the same "type-safeness" by using a structure instead of a class, and achieve the goal of keeping a small/cheap data object that lives on the stack. IMO an enum is safest. The statement you're trying to protect against is just bad code. – Joel Etherton Jan 22 '10 at 20:14
  • 1
    @stakx: I'm just a little more ruthless I guess. If someone were to misuse my enums I'd say he deserved the grief that went along with it >:) – Joel Etherton Jan 22 '10 at 20:37

6 Answers6

31

Enums are great for lightweight state information. For example, your color enum (excluding blue) would be good for querying the state of a traffic light. The true color along with the whole concept of color and all its baggage (alpha, color space, etc) don't matter, just which state is the light in. Also, changing your enum a little to represent the state of the traffic light:

[Flags()]
public enum LightColors
{
    unknown = 0,
    red = 1,
    yellow = 2,
    green = 4,
    green_arrow = 8
}

The current light state could be set as:

LightColors c = LightColors.red | LightColors.green_arrow;

And queried as:

if ((c & LightColors.red) == LightColors.red)
{
    //Don't drive
}
else if ((c & LightColors.green_arrow) == LightColors.green_arrow)
{
    //Turn
}

Static class color members would be able to support this multiple state without extra functionality.

However, static class members are wonderful for commonly used objects. The System.Drawing.Color members are great examples as they represent a known-name colors that have obscure constructors (unless you know your hex colors). If they were implemented as enums you would have to do something like this every time you wanted to use the value as a color:

colors c = colors.red;
switch (c)
{
    case colors.red:
        return System.Drawing.Color.FromArgb(255, 0, 0);
        break;
    case colors.green:
        return System.Drawing.Color.FromArgb(0,255,0);
        break;
}

So if you've got an enum and find that your constantly doing a switch/case/if/else/whatever to derive an object, you might want to use static class members. If you're only querying the state of something, I'd stick with enums. Also, if you have to pass data around in an unsafe fashion enums will probably survive better than a serialized version of your object.

Edit: @stakx, I think you stumbled on something important, too in response to @Anton's post and that is complexity or more importantly, who is it complex for?

From a consumer's standpoint, I would immensely prefer System.Drawing.Color static class members over having to write all of that. From a producer's standpoint, however, it would be a pain to have to write all of that. So if other people are going to be using your code you might be saving them a lot of trouble by using static class members even though it might take you 10 times as long to write/test/debug. However, if its just you you might find it easier to just use enums and cast/convert as needed.

Sometowngeek
  • 597
  • 1
  • 6
  • 27
Chris Haas
  • 53,986
  • 12
  • 141
  • 274
  • Nice answer, Chris. I've accepted your answer because I feel that it brings together several good points nicely. – stakx - no longer contributing Jan 22 '10 at 20:55
  • 2
    Enums are great, we should make enums great again. – Andreas May 16 '17 at 21:39
  • What is the "[Flags()]" thing above the Enum and what does it do? – dsanchez Aug 15 '19 at 15:27
  • 1
    @dsanchez `[Flags()]` is an attribute that indicates the set of values inside can be treated as a set of bit flags. This [example from the MSDN documentation](https://learn.microsoft.com/en-us/dotnet/api/system.flagsattribute) demonstrates the `[Flags()]` attribute being used to return multiple values of the enum by using a bitwise OR – The Fluffy Robot Jan 26 '20 at 01:00
  • Here's a nice explanation on the use of flags: https://stackoverflow.com/a/8480/7389293 – carloswm85 Jun 23 '22 at 23:09
3

Some things I found in the meantime, if anyone else is interested:

  • switch blocks won't work with an enum-as-class.

  • User empi mentioned the similarity of the above enum-as-class sample to Java enums. It seems that in the Java world, there is a recognised pattern called the Typesafe Enum; apparently this pattern goes back to Joshua Bloch's book Effective Java.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • IMO, `switch` is an anti-pattern because it creates multiple touch points when adding an enum value. The code in the `switch` should be a method in the class. – Doug Domeny Mar 29 '18 at 19:35
  • **switch blocks won't work with an enum-as-class** actually, it was an advantage that time, blocking us from anti-pattern... And now we can do pattern matching in Switch expression, muuuccchh better. – Marcelo Scofano Diniz Aug 10 '21 at 13:47
3

I have actually been struggling with something like this quite a bit at work.

It was based on an example that was posted by John B in Jon Skeet's blog article "Enhanced enums in C#".

What I did not get to work properly without a LOT of ugliness was extending an enumeration by deriving a base enumeration class and adding additional static fields of the deriving type.

If you check the basics that are posted in that blog you'll notice that deriving classes will share a common set of values which have serious typing issues and when a certain enumeration base class is derived in two different other classes their value sets will be in the same collection too.

I mean, you'd be hard pressed to create a variable of DerivedEnumClass1 and having to store a value from it's enumeration collection which is actually of type BaseEnumClass1 in that variable without workarounds.

Another problem was Xml Serialization which we use a lot. I got around that using two generic classes as datatypes on enumeration variables on our business objects: one that represented a single enumeration value and one that represented a set of enumeration values which I used to mimic flags enumeration behaviour. They handled xml serialization and deserialization of the actual values that were stored in the properties they were representing. A couple operator overloads were all that was necessary to recreate bitflag behaviour this way.

Those are some of the major issure I ran into.

All in all I'm not very pleased with the end result, some smelly stuff in there but it does the job for now.

Why our chief architect decided to still try and get it to work was the possibilities of having actual classes and extending them with all kinds of behaviour, either generic or specific. Thus far I have not seen a lot I wouldn't have been able to provide with extension methods, but we might run into something.

Don't have any of the code here and I'm not gonna be able to get to it for the weekend otherwise I could have shown where I had gone with it. Not very pretty though... :o

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
Anton
  • 5,323
  • 1
  • 22
  • 25
  • Interesting blog article that you linked to! -- One thing that I was a little concerned about with the enum-as-class approach was that the code for a simple `enum` looks much nicer. Implementing an enum as a class can complicate the code a lot, and IMHO perhaps the point at which it gets **too** complicated is quickly reached? – stakx - no longer contributing Jan 22 '10 at 20:12
  • The code for the actual enums themselves is not that complicated at all. You have the generic class to put all basic functionality in that you require from your expanded enumerations. The enumerations themselves then boil down to no more than the static fields and a private constructor. Plus any specific functionality for that one enum. I spent most of the time on allowing developers to use them like regular enumerations/bit flags. But yeah, you'd better have a very good reason to start using enums like this. – Anton Jan 22 '10 at 20:46
1

I would use the class constants when I have to interop with some legacy code that wants some obscure bit flag values that might be or'd together. In this case the enum might look like

public enum Legacy : ushort
{
  SomeFlag1 = 0x0001,
  SomeFlag2 = 0x0002,
  // etc...
}

Then the marshalling at the P/Invoke is less readable because of the casting needed to translate the enum to the appropriate value.

If it were a const class variable a cast wouldn't be needed.

cmw
  • 862
  • 6
  • 10
1

Both approaches are valid. You should choose per case.

I can add that enums support bit operations as flags (there's even [Flags] attribute to support this semantics and produce pretty strings from enums).

There's a very similar refactoring named Replacing Enums with the Strategy Pattern. Well, in your case it's not quite complete, since your Color is passive and does not act like a strategy. However, why not think of it as a special case?

Alex P
  • 26
  • 3
0

Yup, the original edition of "Effective Java" by Joshua Bloch, which was released prior to Java 1.5 and native support for enums in Java, demonstrated the Typesafe Enum pattern. Unfortunately, the latest release of the book targets Java > 1.5, so it uses Java enums instead.

Second, you can't cast from int to enum in Java.

Color nonsenseColor = (Color)17; // compile-error

I can't speak for other languages though.

organicveggie
  • 559
  • 5
  • 22