Introduction
Do you enjoy typing enum.parse
to convert values in enumerators? Want to use other data types for your enumerator? Do you like to have the actual values customizable at run-time? Like it to be easy to document the values using C# commenting? Yeah? Well here's another way to look at the enumerators.
Background
Microsoft has provided us with the enum
class. It actually does a lot of nice things when working with the enumerators. Unfortunately, it supports only integral types except char
. Luckily for me, I could work with many different types and objects. At times, I really want to treat specific values of these types like constants. Also, the enum.parse
just irked me.
Issues
C# didn't come with the enumerator types to support other data types and to have runtime generated values.
What can we do?
C# along with the .NET libraries provide us with a solution. It involves inheritance and a bit of reflection. The results are simple to work with and can be extended as and when needed.
The approach
Should I try to extend enum
or start over again? I chose to start over. I wanted to make it easy to declare and document. I wanted to have the code handy to make changes. Could it be done with inheritance and reflection so that I could write something like:
[Serializable()]
public class TestStringEnumCommonABC : StringEnumCommon
{
public static readonly string A = "A";
public static readonly string B = "B";
public static readonly string C = "C";
}
When I want to set a value to "A", it would write the code like:
TestStringEnumCommonABC test = new TestStringEnumCommonABC;
test = "A";
test = TestStringEnumCommonABC.A;
Looks easy enough.
How can we make this happen?
The approach taken here uses an abstract
class that has a few methods already defined. We're also using .NET 1.1. It was declared as an abstract
to force the developer to inherit from it. The class has a few static
members:
private static HybridDictionary _values = new HybridDictionary();
private static HybridDictionary _names = new HybridDictionary();
private string _value = "" ;
public readonly string StringNull = "";
public readonly string StringNotApplicable = "NOTAPPLICABLE";
_values
is a type member, it will hold the values that the coder creates in the child class. I went with the HybridDictionary
, it looked as if it would work well. If you know that your project would use the base class more than ten times, you can switch it to a Hashtable
. While it would have been nice to have a static array hold the values for each child class inherited from EnumeratorCommon
, the static values are shared by all the children in .NET 1.1. _names
will hold the names associated with the values in the child classes. Regarding the value for each instance, I figured I'd let .NET work for me. So, _value
will actually hold the value at the instance level. StringNull
and StringNotApplicable
are my way of dealing with two critical logical points most developers and architects inappropriately address. Perhaps, I'll get around to write an article for them later. Notice _value
, StringNull
, and StringNotApplicable
have been declared of type string
.
I did have a choice on how to manage the values created by the developer in the child class:
- Use reflection over and over again when working with the values.
- Associate the values with each instance.
- Associate the values with the type.
- Associate the values with each child type.
1. looked too slow. 2. actually held some promise and I left the code for it in the sample project. It just took up too much memory and required a little extra processing. 3. is a happy medium. 4. may actually be possible, but still may require some interfaces, reflection, and coder intervention to make it work. How does a parent set a value in a child class? How do you enforce that the child class properly implements certain members? Again I would use interfaces and abstract properties. It may still be possible, but I decided to settle on approach 3. I'm sure there are others and hopefully we'll find out about those later.
So, how and when do we load the values from the child. I decided to use the constructor in EnumCommon
to hold the logic that loads _values
. This can be varied, but at least this provides the basic code:
public StringEnumCommon()
{
Type t = this.GetType();
if (_values.Contains(t.FullName) == false)
{
FieldInfo[] fields =
t.GetFields(BindingFlags.Public | BindingFlags.Static );
int numFields = fields.Length;
string[] values = new string[numFields +2];
string[] names = new string[numFields + 2];
for (int i =0; i<numFields ; i++ )
{
if (fields[i].IsInitOnly == true &&
fields[i].FieldType == _value.GetType() )
{
string addValue = (string)fields[i].GetValue(null) ;
for ( int j= 0; j < values.Length; j++)
{
if ( (string)values[j] == addValue)
{
throw new NotSupportedException("Value " +
"with duplicate names not supported.");
}
}
values[i] =
addValue;
names[i] = fields[i].Name;
}
if (fields[i].IsInitOnly == true &&
fields[i].FieldType != _value.GetType() )
{
throw new ArrayTypeMismatchException("A " +
"constant with a different type from "
+ "the value's type has been declared. Look for "
+ fields[i].FieldType.Name );
}
}
values[values.Length-2] = StringNull;
names[values.Length-2] = "StringNull";
values[values.Length-1] = StringNotApplicable;
names[values.Length-1] = "StringNotApplicable";
_values.Add(t.FullName,values);
_names.Add(t.FullName,names);
}
}
First the constructor gets the type, which at this point is the child class' type. It then checks to see if _values
already contains the values for the child type. If it does not, reflection is used to cycle through the public, static fields and fields that are set to readonly (IsInitOnly
). Other checks are done to ensure that the value is loaded once and to make sure the type of the field matches the type of _value
. If the value from the field looks good, it's loaded into a string array. Once cycling through the fields is completed, the array is placed in _values
with the child type as the key. At the same time the _names
array is loaded with the names associated with the values in the child class.
So why did we load up the values and the names into arrays. Performance! When the value is set, we don't want to allow the coder to set a value that the coder has not declared in the child type. So rather than using slowfection, we can rip through the array. We use the names array to keep it in synch with the values and to help make the helper functions work faster:
public string Value
{
get { return _value; }
set
{
bool found = false;
if (value == StringNull || value == StringNotApplicable)
{
_value = value;
found = true;
}
else
{
Type t = this.GetType();
string[] values =
(string[])_values[this.GetType().FullName];
foreach (string v in values)
{
if ( v == value )
{
_value = value;
found = true;
break;
}
}
if (found == false)
{
throw new ArgumentOutOfRangeException(value +
" not found in enumeration.");
}
}
}
}
Remember _value
belongs to the instance, not the class type. _values
, which belongs to the abstract class contains the arrays of values that are declared by the coder in the child class. Notice that the values of StringNull
and StringNotApplicable
are allowed no matter what the coder declares.
From here we can add the code that makes life easier for the coder, the helper functions:
public string Name
{
get {return GetNameForValue(_value);}
}
public string[] GetValues()
{
string[] values = (string[])_values[this.GetType().FullName];
return (string[])values.Clone();
}
public string[] GetNames()
{
string[] names = (string[])_names[this.GetType().FullName];
return (string[])names.Clone();
}
public string[,] GetNamedValues()
{
string[] values = (string[])_values[this.GetType().FullName];
string[] names = (string[])_names[this.GetType().FullName];
string[,] namedValues = new string[values.Length,2];
for (int i =0;i<values.Length ; i++ )
{
namedValues[i,0] = (string)names[i] ;
namedValues[i,1] = (string)values[i];
}
return namedValues;
}
public string GetNameForValue(string forValue)
{
string[] values = (string[])_values[this.GetType().FullName];
string[] names = (string[])_names[this.GetType().FullName];
string name = "";
for (int i =0;i<values.Length ; i++ )
{
if (forValue == (string)values[i])
{
name =(string)names[i] ;
}
}
return name;
}
public DataTable GetNamedValuesTable()
{
string[] values = (string[])_values[this.GetType().FullName];
string[] names = (string[])_names[this.GetType().FullName];
DataTable dt = new DataTable();
DataColumn dc = new DataColumn();
DataRow dr;
dc.DataType = System.Type.GetType("System.String");
dc.ColumnName = "Name";
dc.AutoIncrement = false;
dc.Caption = "Name";
dc.ReadOnly = true;
dc.Unique = true;
dt.Columns.Add(dc);
dc = new DataColumn();
dc.DataType = System.Type.GetType("System.String");
dc.ColumnName = "Value";
dc.AutoIncrement = false;
dc.Caption = "Value";
dc.ReadOnly = true;
dc.Unique = true;
dt.Columns.Add(dc);
for (int i =0;i<values.Length ; i++ )
{
dr = dt.NewRow();
dr["Name"] = (string)names[i] ;
dr["Value"] = (string)values[i];
dt.Rows.Add(dr);
}
dt.AcceptChanges();
return dt;
}
GetValues
returns all the values. GetNames
returns the names associated to the values. GetNamedValues
returns a two dimensional array with the name and value. GetNamesValuesTable
is the same as GetNamedValues
, but puts them into a DataTable
. GetNameForValue
returns the name associated with a value.
Performance
To help my analysis of these classes, I used an open source tool called Zanebug. It can be found here. It can do a lot of cool things that NUnit can't do right and it's fairly easy to work with.
I made two different versions for managing the values and names. StringEnum
has the array of values declared per instance and cycles through the names with reflection. StringEnumCommon
uses two hybrid collections declared statically to hold the names and the values.
In my testing, I was concerned with the object creation time, time to set a value, and memory usage. Using Zanebug, it pretty much confirmed that StringEnum
for one instance would work faster for instantiation, work faster for setting values, and use more memory. StringEnumCommon
after the first instantiation took much less time to create an instance (0.0147 vs. .0008), took longer to set a value (0.00044 vs. .0011), and less memory. So there's definitely a give and take when it comes to working with the different approaches. Is one approach better than the other? It depends on what the application needs to perform. Luckily, both approaches are easy for the coder to implement and help make the application development quicker and more accurate.