Repurposing Enumerations (from article on aspalliance)

Repurposing Enumerations

Introduction
Enumerations provide the ability to name and then group integral values. Using an enumeration a programmer can provide a set of named values for a variable, property, or parameter.  .NET has made enumerations even friendlier. Base functionality in the .NET Enum class lets you retrieve the names of an enumeration member, retrieve name values as an array, and bind them to controls as a data source. This functionality, coupled with the use of a TypeConverter class, allows a programmer to use enumerations in novel ways.

 

This article highlights a case where TypeConverter classes were used to repurpose enumerations in order to provide a friendly façade to a third-party API that expects parameterized string values.

 

Problem Description
Enumerations are useful because they provide the ability to put a friendly name to any integral value except char. Additionally, they are a design time construct so you can use dot “.” notation and Intellisense to interact with them. Such as:

 

// define it

public enum MyEnumeration

{

   FriendlyName1=0,

   FriendlyName2

}

 

// use it

protected MyEnumeration localEnum=MyEnumeration.FriendlyName1;

 

However, there is not a similar construct for putting a friendly name to a set of constant string values. A typical implementation would be to define a set of static properties or public read-only string values on a class. Although a valid approach, it has a couple of limitations:

1.      An enumeration is a specialized typed value. A static property or constant string value is not. For example:

// example of specialized value using enum

// valid values are FriendlyName1 and FriendlyName2

public SomeMethod(MyEnumeration enumParameter)

{

    .. .. ..

}

 

// can’t do this with strings, any string value will do.

public SomeMethod(string stringParameter)

{

    .. .. ..

}

 

2.      If there is a limited set of valid string values, then you will need to write some special validation code. Additionally, the meaning behind the string values may or may not be easy to remember.

In order to make the points real, let’s discuss a real world situation. Through my company Cybral, I implement various .NET XML web service solutions. At one particular client, I am interacting with a third-party API to create new work orders in a billing system. This billing system requires a set of parameterized string values to define the type of work order being created. To make it really simple, let’s say there are just four valid values:

1.      "CSRVN" means change services and send a technician.

2.      "CSRVY” means change services but do not send a technician.

3.      "INSTN" means install services and send a technician.

4.      "INSTY" means install services but do not send a technician.

Reading through these codes, you soon realize that they are painful to use. Now multiply this problem by 100 because there are a large number of these situations.

Now, we could create four different functions on a class that expose these different tasks, using a friendly name for each method. For example, ChangeServiceNoTechnician () or ChangeServiceOfferingWithTechnician(). However, what if there are other parameter values that can be combined with these codes? Soon we would have so many methods that we would be creating more problems then we solve.

Enumerations are the answer. But remember these are string values, so how do we generically handle converting these enumerated values to values the underlying API can understand? If the converted value is an integral value, then the default Enum class can handle everything. If we must translate them to other string values, we will have to create a converter class of some sort. Fortunately for us, Microsoft has thought of this too and has provided the TypeConverter.

 

TypeConverters
TypeConverters are the answer, specifically a derivation of TypeConverter called EnumConverter. By deriving from EnumConverter we can change how an enumeration is converted to and from a string value. Furthermore, through the use of attribute-based programming, we can assign the converter to our newly defined enumeration so that at runtime we can query for the TypeConverter class and then convert the enumeration value on the fly. Slick? You bet!

 

Defining the Enumeration

Notice the TypeConverter(typeof(TaskCodeConverter))] attribute.

/*

** Here we are defining our enumeration that we will expose to the

** outside world. We are using the TypeConverterAttribute to assign

** our Converter to this enumeration e.g.

** [TypeConverter(typeof(TaskCodeConverter))]. This enables

** us to query for it at runtime so that we can convert values

** on the fly.

*/

[TypeConverter(typeof(TaskCodeConverter))]

public enum eTaskCode

{

      ChangeOfServiceSendTechnician,

      ChangeOfServiceNoTechnician,

      InstallServiceSendTechnician,

      InstallServiceNoTechnician,

}

 

Defining the Custom EnumConverter

// This is our TypeConverter class. Notice that we are deriving

// from EnumConverter

public class TaskCodeConverter : EnumConverter

{

      // Here we are defining the values that the API expect.

      // Note: public read-only constants are still a great way

      // of exposing a strict set of valid values. I just don’t

      // want to expose them to users of my webservices or

      // my businesslogic layer.

      #region constants

      public const string CSRVN="CSRVN";

      public const string CSRVY="CSRVY";

      public const string INSTN="INSTN";

      public const string INSTY="INSTY";

      #endregion constants

 

      #region ctors

      // The default constructor (define the enum this class supports).

      public TaskCodeConverter ():base(typeof(eTaskCode)){}

      #endregion ctors

 

      #region public methods

      // This OVERRIDDEN method handles converting “From” a specific

      // type. We only care about string values, so we are only handling

      // string types. Any other type is passed up to the base type

      // EnumConverter.

      // This method is all about converting TO our Enumeration

      // eTaskCode. Nothing else should ever be returned.

      public override object ConvertFrom(ITypeDescriptorContext

          context,system.Globalization.CultureInfo culture,

          object value)

      {

         // remember we only care about strings…………

         if(value as string != null)

         {

            // it’s a good idea to clean up the string…………

            string localTask=((string)value).Trim().ToUpper();

            // If they pass us nothing, then throw an exception.

            if(localTask.Length == 0)

            {

                  throw new ArgumentNullException("taskCode");

            }

            else

            {

                  // compare what we got with out constants defined above.

                  // only handle defined values.

                  switch(localTask)

                  {

                        case CSRVN:

                           return eTaskCode.ChangeOfServiceSendTechnician;

                        case CSRVY:

                           return eTaskCode.ChangeOfServiceNoTechnician;

                        case INSTN:

                           return eTaskCode.InstallServiceSendTechnician;

                        case INSTY:

                           return eTaskCode.InstallServiceNoTechnician;

                        default:

                           throw new ArgumentException(string.Format("Invalid

                              taskCode value: {0}",localTask));

                  }

            }

         }

         else

         {

            // all other types get passed up to the base class EnumConverter.

            return base.ConvertFrom(context,culture,value);

         }

      }

      // This OVERRIDDEN method handles converting “To” a specific

      // type. We only care about string values, so we are only handling

      // string types. Any other type is passed up to the base type

      // EnumConverter.

      // This method is all about converting FROM our Enumeration eTaskCode

      // to a value of a specified type. Since we want to return a value

      // that the API understands then that is what we return.

      public override object ConvertTo(ITypeDescriptorContext context,

            System.Globalization.CultureInfo culture, object value,

            Type destinationType)

      {

            // check if we are returning a string.

            if(destinationType==typeof(string))

            {

                  switch((eTaskCode)value)

                  {

                        case eTaskCode.ChangeOfServiceSendTechnician:

                              return CSRVN;

                        case eTaskCode.ChangeOfServiceNoTechnician:

                              return CSRVY;

                        case eTaskCode.InstallServiceSendTechnician:

                              return INSTN;

                        default:

                              return INSTY;

                  }

            }

            else

            {

                  return base.ConvertTo(context,culture,value,destinationType);

            }

      }

      #endregion public methods

}

 

Example in Action

// Notice that we are using the enumeration.

public double CreateWorkOrder(eTaskCode taskCode,

                              string accountNumber)

{

    ...

   string task = (string)TypeDescriptor.GetConverter(typeof(

      eTaskCode)).ConvertTo(taskCode,typeof(string));

   // task will now contain CSRVN,CSRVY,INSTN or INSTY

    ...

}

 

Conversely, to convert a string value to an eTaskCode value, you can do this.

eTaskCode taskCode = (eTaskCode)TypeDescriptor.GetConverter(typeof(

                         eTaskCode)).ConvertFrom(“CSRVN”);

 

// taskCode will be equal to eTaskCode.ChangeOfServiceSendTechnician

 

Conclusion

This article covered using enumerations and a custom EnumConverter class to handle conversions to and from a set of discrete string values. This approach has a number of advantages and can be used in a number of situations. We find it especially useful when presented with the problem of creating a façade over a third-party API. Some other aspects are:

  • It plugs directly into the .NET Framework and leverages conversion functionality designed into .NET for this purpose.
  • It localizes all conversion code for an enumeration to a single class.
  • Using TypeConverterAttribute(), you can assign your converter class at design time to your enumeration, so that you can retrieve and use it at runtime.

No Comments