Monday, September 1, 2008

Enumerations and Strings

It has been a while since my last post. Things get crazy at times. But, here is another bit of sample code for you to muse over. We will look at enumerations and strings.

In C#, an enumeration is NOT a string, nor can you define it to be one. An enumeration can be defined as any of a number of integer types. You can leave it as its default type...


public enum unspecifiedTypeEnum
{
one = 1, two, three,
}


In which case you get named 32 bit values. Or you can specify the type of the underlying value as byte, sbyte, ushort, short, uint, int, ulong or long...


public enum byteTypeEnum : byte
{
one = 1, two, three,
}
// or...
public enum ushortTypeEnum : ushort
{
one = 1, two, three,
}
// etc...


... But, you can't declare it as a string.

That doesn't mean you can't use strings at all. When I want to save data to a file for future reference, I like to make my enumerations human readable and store them in the file as human-readable. It would be a shame to use enumerations to write clear code only to store them as very cryptic values in my maintenance and configuration files. Getting the string representation given the enumeration name is easy. We just use the object.ToString() overloaded method. For enumerations, this returns the name of the value as a string...


public enum Animals { cat, mouse, bird, dog, }
//....

Animals myAnimal = Animals.cat;
Console.WriteLine("CurrentAnimal={0}",myAnimal.ToString());


When I load a configuration value back in to my program, I would want to work with the value as the original enumeration type. That's a little trickier, but with the help of "reflection", it can be done. This little routine shows how...


using System.Reflection;
// ...

Animals FromString(string animal)
{
Type t = typeof(Animals);
FieldInfo[] fi = t.GetFields();

try
{
foreach (FieldInfo f in fi)
{
if (f.Name.Equals(animal))
{
return (Animals)f.GetRawConstantValue();
}
}
}
catch
{
}
throw new Exception("Not an Animal");
}

Actually, it's not so tricky. A reader (thankyou Paul) pointed me to the "static" Enum functions which do much of the hard work for you. The function above can be more simply written as...

Animals FromString(string animal)
{
try
{
return (Animals)Enum.Parse(typeof(Animals), animal);
}
catch (ArgumentException ex)
{
throw new Exception("(" + animal + ")" is not in the Animals enumeration.",ex);
}
}


This gives me an enumeration value for my string, assuming the string matches one of the enumeration names. Enhancements could be made to this code to store and retrieve the fully qualified name. I'll leave that as an exercise for the reader.

You may be wordering about the try and catch above. The GetRawConstantValue() returns a value that we cast to our enumeration type. If that value is invalid for our enumeration, then the cast throws an exception. Also, understand that there are other values in the FieldInfo array besides just enumeration fields. Enumerations have hidden fields that are accessible through reflection. Under contrived circumstances, the caller may pass in the name of one of these hidden fields. The "animal" name will match a field in the FieldInfo array, but the cast with throw an exception as we want it to.

Now, with the ability to convert between string and enumeration, you can write your code so that everything internal is performed on the enumeration type, while persistance and other external representation can be strings. But, what if we want a list of all possible values. We might let the user choose from a list, how might we get that? Here's a way to create a string list for the enumeration...


// also requires
using System.Reflection;
...

IEnumerable AnimalsList()
{
Type t = typeof(Animals);
foreach (FieldInfo f in t.GetFields())
{
try
{
if (f.GetRawConstantValue() is Animals)
;
}
catch
{
continue;
}
yield return f.Name;
}
}

...or simply ...

IEnumerable AnimalList()
{
return Enum.GetNames(typeof(Animals)).ToList();
}


Now in your Form constructor you can write the following to show your enumerations as strings.


listBox1.Items.AddRange(AnimalsList().ToArray());


Someone will invariably want to associate a different string to their enumeration than the name. Maybe they want to obscure the meaning. More likely, the programmer wants to change the enumeration after there are already configuration files in production using older enumeration names. Changing the enumeration could break existing configuration files. But you have to deal with that regardless of how you store the data. I won't go into code examples, but a Dictionary object could be used internally. Another possibility is the string name and the enumeration name may differ by case only, but it should be a trivial modification to deal with that condition, so I leave that also as an exercise for the reader.

Finally, these enumeration functions are perfect for making Generic. They could be used on several enumerations in our code, and we wouldn't want to re-implement it each time. So, I will leave you with the following generic example of Enumerations and Strings.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

namespace EnumExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listBox1.Items.AddRange(EnumList().ToArray());
}

public enum Animals { dog = 1, cat, mouse, bird, }

private void button1_Click(object sender, EventArgs e)
{
Animals myAnimal = Animals.bird;
Console.WriteLine("CurrentAnimal={0}", myAnimal.ToString());

myAnimal = FromString("cat");
try {
myAnimal = FromString("value__");
} catch (Exception ex) {
Console.WriteLine("error: {0}", ex.Message);
}
}

T FromString(string animal) where T : struct
{
Type t = typeof(T);
FieldInfo[] fi = t.GetFields();

try
{
foreach (FieldInfo f in fi)
{
if (f.Name.Equals(animal))
{
return (T)f.GetRawConstantValue();
}
}
}
catch
{
}
throw new Exception("Not a Type " + typeof(T).Name);
}

IEnumerable EnumList()
{
Type t = typeof(T);
foreach (FieldInfo f in t.GetFields())
{
try
{
if (f.GetRawConstantValue() is T)
;
}
catch
{
continue;
}
yield return f.Name;
}
}
}
}

2 comments:

Paul said...

Thought you might be interested in knowing about the static methods in the Enum class that makes it really simple to parse a string to an Enum.

The Enum class has a Parse() method that converts a string to an Enum. Below is how you could update your FromString() method to use this.

Animals FromString(string animal)
{
try
{
return (Animals)Enum.Parse(typeof(Animals), animal);
}
catch (ArgumentException)
{
throw new Exception("Not an Animal");
}
}

The Enum class also has a GetNames() method that returns an array of the enum values. So you could update your AnimalList() method to look like this:

string[] AnimalList()
{
return Enum.GetNames(typeof(Animals));
}

Hope this helps.

Les Potter - Xalnix Corporation said...

Excellent Paul! I was unfamiliar with the Enum static methods. I'll probably go back and add your contribution. At least the part on "reflection" is somewhat useful (and the code itself is instructive for other things).

Thanks again.