Projects — 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<EnumType>.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<Captain>.Parse("Picard");
 
/** Get an enum constant from an integral type. */
 
// Enum
Captain tyrant = (Captain) Enum.ToObject(Captain.GetType(), 3);
 
// Dnum
Captain tyrant = Dnum<Captain>.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<Captain>.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<Captain>.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<Color>.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<Color>.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<Color>.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<Color>.Parse("Red");
Color red2 = Dnum<Color>.Parse("red", true /* Ignore case */);
Color red3 = Dnum<Color>.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<Color>.Parse("Light Blue");
 
// Works fine.
Color lightBlue2 = Dnum<Color>.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<Produce>.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<Produce>.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<Alignment>.Parse("0");
// == Alignment.Top
 
// Parsing a string representation of two constants.
var bottomLeft = Dnum<Alignment>.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<Number>.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.

2 Comments so far

  1. [...] these and other problems have been fixed in my Dnum library [...]

  2. DotNetKicks.com on September 22nd, 2009

    Dnum…

    You’ve been kicked (a good thing) – Trackback from DotNetKicks.com…

Leave a reply

*