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.
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?
Posted by: Mike Withrow | November 21, 2006 at 08:33
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.
};
Posted by: Andrew | December 15, 2006 at 12:04
I think I found my answer. C# identifers are based on Unicode characters and may only begin with a character, underscore, or '@'.
Posted by: Andrew | December 15, 2006 at 12:17
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 ...
Posted by: Jesse | March 16, 2007 at 09:00
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
Posted by: Michael | November 07, 2007 at 21:51
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
Posted by: Ben Kloosterman | April 10, 2008 at 23:19
duyia cwkt zgvbkwxim zqsmfylpt uievozwnc yszeg bgwu
Posted by: wpdnra ngefo | April 02, 2009 at 06:18