Dnum
Dnum is a static class for accessing enumeration values much like the built-in .NET Enum class. Unlike Enum, Dnum is type-safe, reducing the number of enum-related runtime errors and eliminating the need for tedious, error-prone and inelegant down-casts.
Dnum also transparently supports the DescriptionAttribute attribute, which allows you to conveniently use spaces and other characters in your enumeration constants.
Dnum 1.0.0-rc1 is available from the Dnum Google Code site.
Dnum is licensed under the GNU LGPL.
Using Dnum with plain, old C# enums
Dnum offers all your favourite Enum class methods in a new, easy-to-digest format. As a rule of thumb, everywhere you wrote
Enum.Method( typeof(EnumType), arg1, arg2, ..., argN )
replace that with
Dnum.Method( arg1, arg2, ..., argN )
You should also remember that the ToObject
method of Enum has been replaced with ToConstant
in Dnum.
enum Captain : byte { Kirk, Picard, Sisko, Janeway, Archer }
/** Get an enum constant from a string. */
// Enum
Captain best =
(Captain) Enum.Parse(Captain.GetType(), "Picard");
// Dnum
Captain best = Dnum.Parse("Picard");
/** Get an enum constant from an integral type. */
// Enum
Captain tyrant = (Captain) Enum.ToObject(Captain.GetType(), 3);
// Dnum
Captain tyrant = Dnum.ToConstant(3);
/** Type safety. */
// This will compile but throw an exception at runtime.
string bogus = Enum.GetName(Captain.GetType(), DateTime.Today);
// The same code using Dnum will not compile, allowing you to
// catch the error at compile time.
string bogus = Dnum.GetName(DateTime.Today);
/** No silent overflowing when converting to constant. */
// Enum
// This returns an instance of Captain.Kirk.
Captain surprise = (Captain) Enum.ToObject(Captain.GetType(), 256);
// Dnum
// This throws an overflow exception.
Captain surprise = Dnum.ToConstant(256);
Using Dnum with described enums
A described enum is the same a regular enum with a string attached to each constant. Consider the following enum use case:
enum Color { Red, Blue, Green, Yellow }
Console.Write("This program supports the following colors:");
var colors = Dnum.GetNames().ToArray();
Console.Write( String.Join( ",", colors ) );
In this case, we are using an enum to enumerate the colors supported by an application. This works fine and dandy right up to the point where you want to support a color like "Light Blue".
enum Color { Red, LightBlue, Green, Yellow }
Console.Write("This program supports the following colors:");
var colors = Dnum.GetNames().ToArray();
Console.Write( String.Join( ",", colors ) );
This code works, but it prints out "Red, LightBlue, Green Yellow" instead of a much more user-friendly "Red, Light Blue, Green, Yellow".
How do we get around this? You guessed it, we add a description attribute (that, conveniently, is built-in to the .NET framework):
enum Color
{
Red,
[Description("Light Blue")] LightBlue,
Green,
Yellow
}
Console.Write("This program supports the following colors:");
var colors = Dnum.GetDescriptions().ToArray();
Console.Write( String.Join( ",", colors ) );
This code now prints out "Red, Light Blue, Green, Yellow". You'll notice we used GetDescriptions
instead of GetNames
. This is because we want to get the description value if it's defined. GetDescriptions
will return the description of each constant in the enum. If GetDescriptions
doesn't find a DescriptionAttribute
, it uses the string value of the constant instead.
Using Dnum to parse strings
There are 2 methods that Dnum provides for parsing strings into enum constants: Parse
and ParseDescription
. Each method has a companion method withthe Try
prefix, similar to the Int32.TryParse
or DateTime.TryParse
methods from the .NET framework library.
The Dnum.Parse
method works identically to the regular .NET Enum.Parse
, so no new surprises there (other than the improved syntax). Here's some examples of it in action:
enum Color
{
Red,
[Description("Light Blue")] LightBlue,
Green,
Yellow
}
Color red1 = Dnum.Parse("Red");
Color red2 = Dnum.Parse("red", true /* Ignore case */);
Color red3 = Dnum.Parse("0");
// red1 == red2 == red3
Keep in mind that the Dnum.Parse
method pays no attention to description attributes.
// Throws an exception.
Color lightBlue1 = Dnum.Parse("Light Blue");
// Works fine.
Color lightBlue2 = Dnum.Parse("LightBlue");
In order to get an enum constant from a description, you must use the Dnum.ParseDescription
method.
ParseDescription
works a little differently than Parse
since description attributes need not be unique, and can contain any character. In most circumstances, these limitations will not be an issue, but they're still worth pointing out.
Let's look at the uniqueness problem. Consider the following code.
enum Produce
{
[Description("Fruit")] Apple,
[Description("Vegetable")] Carrot,
[Description("Vegetable")] Cucumber,
[Description("Fruit")] Orange,
[Description("Vegetable")] Tomato
}
// What does this variable hold: Apple or Orange?
var result = Dnum.ParseDescription("Fruit");
Since description attributes are not unique, we have two matches for the "Fruit" description. Which one will be returned? Since you might want both, Dnum will return both of them, in the order that they appear in the enum.
var result = Dnum.ParseDescription("Fruit");
foreach (var fruit in result) Console.WriteLine(fruit);
// Prints:
// Apple
// Orange
// Get the first constant with the "Fruit" description.
var apple = result.First();
Now, let's look at how ParseDescription
works around the fact that description attributes can contain any character. First, let's look at two things Parse
can do that ParseDescription
cannot.
[Flags] enum Alignment
{
Top = 0,
Bottom = 1,
Left = 2,
Right = 4
}
// Parsing a string representation of a constant value.
var top = Dnum.Parse("0");
// == Alignment.Top
// Parsing a string representation of two constants.
var bottomLeft = Dnum.Parse("Bottom, Left");
// == Alignment.Bottom | Alignment.Left
Why can't ParseDescription
work in the case of parsing a string representation of a constant value? It's due to the fact the string representation of a constant value can possibly a description. For example, consider this case:
enum Number
{
[Description("1")] One,
[Description("2")] Two,
[Description("3")] Three
}
// Should it contain Number.One or Number.Two?
var one = Dnum.ParseDescription("1").First();
Because of this ambiguity (Number.Two has a constant value of 1 but Number.One has a description of "1"), ParseDescription
always treats it's description argument as a description, regardless of whether it contains a numeric value or a comma.