Other Bloggers

  • Graham Glass
    Entreprenurial free-thinker who loves adventure.
  • Danil Suits
  • Brian Oxley
    Former ThoughtWorks colleague, fellow Rice alum.
  • Jef Newsom
    I am a small business owner with a passion for my family, music, and leveraging technology to create winning business solutions.
  • Greg Vaughn
    Agile-minded developer in the mortgage industry.
Blog powered by TypePad

« What's So Bad About Smalltalk? | Main | getopt in Java, TDD-style »

January 23, 2004

More C# 'enum' Wackiness

Chris Stevenson rants about C# enums here. I thought I'd share some of my own observations about C# enums, from earlier in 2003, previously shared only amongst co-workers.

Ostensibly enums are a way to define a type such that variables of that type can assume one of only a fixed set of values. For example:

using System;
namespace Pholser.Sandbox.Enum
{
  public enum CardSuit
  {
    CLUBS = 1,
    DIAMONDS,
    HEARTS,
    SPADES
  }
  public enum CardRank
  {
    ACE = 1,
    TWO,
    THREE,
    FOUR,
    FIVE,
    SIX,
    SEVEN,
    EIGHT,
    NINE,
    TEN,
    JACK,
    QUEEN,
    KING
  }
public class PlayingCard
{
    private CardRank rank;
    private CardSuit suit;
    public PlayingCard( CardRank rank, CardSuit suit )
    {
      this.rank = rank;
      this.suit = suit;
    }
    public CardRank Rank
    {
      get { return this.rank; }
    }
    public CardSuit Suit
    {
      get { return this.suit; }
    }
  }
}

In reality, they are a collection of integer constants. Their values can be inferred from the order and manner in which they are declared. And, it is ridiculously easy to subvert the type safety they purport to offer.

The compiler does recognize when you've passed integers in place of an enum value, even if the integer value corresponds to an enum value:

    PlayingCard fiveOfHearts =
      new PlayingCard( 4, 1 );

But you can cast any integer value to the enum type, including values that are not in the enum:

    PlayingCard fiveOfHearts =
      new PlayingCard( (CardRank) 4, (CardSuit) 1 );
    PlayingCard rulesCard =
      new PlayingCard( (CardRank) 343, (CardSuit) -1 );

In fact, zero does not even require the cast:

    PlayingCard joker =
      new PlayingCard( 0, 0 );

To cause the abstraction to leak even further, you can annotate the enum type with the Flags attribute, which enables bitwise-ORs of combinations of the values of the enum to be treated as legal values of the enum, and would appear to modify how == works...assume here that CardSuit is annotated with Flags:

using System;
using NUnit.Framework;
namespace Pholser.Sandbox.Enum
{
  [TestFixture]
  public class PlayingCardTest
  {
    [Test]
    public void InRange()
    {
      PlayingCard jackOfSpades =
        new PlayingCard( CardRank.JACK, CardSuit.SPADES );
      Assert.AreEqual( CardRank.JACK, jackOfSpades.Rank );
      Assert.AreEqual( CardSuit.SPADES, jackOfSpades.Suit );
    }
    [Test]
    public void OutOfRange()
    {
      PlayingCard joker =
        new PlayingCard( (CardRank) 12334, (CardSuit) 23489076 );
      Assert.AreEqual( (CardRank) 12334, joker.Rank );
      Assert.AreEqual( (CardSuit) 23489076, joker.Suit );
    }
    [Test]
    public void Zero()
    {
      PlayingCard joker = new PlayingCard( 0, 0 );
      Assert.AreEqual( (CardRank) 0, joker.Rank );
      Assert.AreEqual( (CardSuit) 0, joker.Suit );
    }
    [Test]
    public void Flags()
    {
      PlayingCard joker =
        new PlayingCard( 0, CardSuit.CLUBS | CardSuit.DIAMONDS );
      Assert.AreEqual( (CardRank) 0, joker.Rank );
      Assert.AreEqual( CardSuit.HEARTS | CardSuit.DIAMONDS, joker.Suit );
      Assert.IsTrue(
        ( CardSuit.HEARTS | CardSuit.DIAMONDS )
        ==
        ( CardSuit.CLUBS | CardSuit.DIAMONDS ) );
    }
  }
}

There are some nice things that enums offer...they all derive from a common superclass (System.Enum) that gives facilities for stringifying values, comparing, etc. Annotating the enum type with the Flags attribute modifies how Parse(), Format(), and ToString() behave on instances, so that you can see all the "set bits" with their enumeration names and create an enum from such a string representation. In fact, they're really nice and give you a means to ensure that an enum value really is in range:

      if ( !Enum.IsDefined( typeof( CardSuit ), suit ) )
        throw new ArgumentOutOfRangeException(
          "suit", suit, "invalid suit value" );

Does this strike anyone else as really odd? Why not go full on and make them type-safe to begin with.

Let's not fool ourselves, this is not as typesafe as one would think---this is pretty close to C++ enums. Whether that's a good thing obviously varies by individual opinion....8^) I was just surprised, as it would seem the trend is toward true typesafe enums.

A response regarding why some of the above is allowed, from Eric Gunnerson of the C# development team:

Enums in C# do dual purpose. They are used for the usual enum use, and they're also used for bit fields. When I'm dealing with bit fields, you often want to AND a value with the bit field and check if it's true.

Our initial rules meant that you had to write:

if ((myVar & MyEnumName.ColorRed) != (MyEnumName) 0)

which we thought was difficult to read. One alernative was to define a zero entry:

if ((myVar & MyEnumName.ColorRed) != MyEnumName.NoBitsSet)

which was also ugly.

We therefore decided to relax our rules a bit, and permit an implicit conversion from the literal zero to any enum type, which allows you to write:

if ((myVar & MyEnumName.ColorRed) != 0)

which is why PlayingCard(0, 0) works.

Sheesh.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d8342a671a53ef00d834c7f49953ef

Listed below are links to weblogs that reference More C# 'enum' Wackiness:

Comments

Besides the first glance I found your site to be very useful although is there a way we can get just a little more green?

It seems the enumerator list must begin with a character. It seems all the named constants below are in error except the last one. In my particular circumstance this is confining.

public enum SamplingFrequency
{
8000, //error, invalid token
9000 = 1,
10000 = 10000,
15caps,
char55 // Named constants must begin with a char.
};

I think I found my answer. C# identifers are based on Unicode characters and may only begin with a character, underscore, or '@'.

what's the big deal? ... the quote you cited from Eric Gunnerson answered all your questions ... did you do that on purpose? don't use an enum if you want something water tight in the same way that you don't use an int if you want a string ...

i seem to agree with the rationale for allowing zero to implicitly denote a no-value enum, until i saw this article:

http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html

can C# team find a more elegant solution for enum that are zero value?


i'm looking forward for them to clean up this enum's zero wackiness


i think implicitly casting zero to enum type is not a good decision

One of the main things enum Flags are used for is C++ interop ..

If you want high performance but low abstraction use enums
1) Enums also have issues with serialization eg it allows a client to send a value of say 12 that does not match a value on a service - however it is valid for the enum . It gets send and somewhere in your service it will crash or you will get an illegal value in a db.

2) You can also use arithmatic on enums eg Red+1 ... = Yellow .. Certainly not intended by most devs.

If you want better programming , make your own enum ie use a class and statics to represent the values = type safe.

If you want performance / C++ interop use enums.

Regards,

Ben

duyia cwkt zgvbkwxim zqsmfylpt uievozwnc yszeg bgwu

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been posted. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment