Click here to Skip to main content
Click here to Skip to main content
Go to top

StringEnum That Works Like System.Enum

, 22 May 2007
Rate this:
Please Sign up or sign in to vote.
A base class for closely simulating enum behavior with the underlying type of string.

Introduction

If you've ever had a list of string constants to maintain, use, and compare, you've probably experienced frustration at the fact that even at .NET 2.0, we don't have a native enum for strings. Further, you can't inherit from the System.Enum class and so can't extend it to support strings.

A StringEnum would be very handy in defining possible database values, holding an enumerable list of string values for populating lists, etc. Many workarounds have been created, but they all have their issues.

Just a warning: the implementation may be a little rough since I just wanted to get something out for people to see and start using right away. That said, feedback is welcome.

Background

I've seen several different methods of achieving something like a true string enum. They are as follows:

  • Enum.ToString - So why not just use a simple Enum and then just use the ToString and Parse functions to convert to and from strings?
    • Advantages include being easy to define in addition to most of the usual Enum advantages.
    • The main disadvantage is that the name is strictly tied to its value. A name like "PF_SCR_ATTRIB_WIDESCREEN" is unnecessarily decorated, and "SA" is unnecessarily abbreviated. They can be better expressed as "Widescreen" or "Sales", respectively. (I know XML comments can mitigate, but we're working for clear, concise code.) Also, you must use the ToString and Parse functions every time you want to work with the string value. Also, all sorting happens by numeric value and the enum class cannot be extended.
  • Attributes - In this method, certain custom attributes are defined and attached to a regular Enum. (A good example of this can be found here.)
    • Since it is an Enum, advantages are all the advantages of Enum -- can get a list of values, can parse name from string, etc.
    • Drawbacks are that a function must be explicitly called to "mine" the attributes to parse a value or return one, and since the function is universal, you have no benefits of Intellisense until you manually type the name of the function, then of the enum, and a ".". Also, the base type is still a number, and so assignments and comparisons are still done on a numeric level.
  • Constants - Using some lesser-known techniques (used in the StringEnum code), defining a set of constants on a class is actually a fairly good method.
    • The advantage is that since your enum values are all string constants, all your comparisons are string comparisons.
    • A disadvantage is that string comparisons are just plain old string comparisons relying on Option Compare or lots of StrComp for case sensitivity. Also, there is no type checking, and no unnamed values are allowed.
  • Structure - An example of this is available here.
    • This is one of the best ways since a structure behaves like a value type, which makes it conceptually easy to track. Operators can be overloaded, and you can implement your own functions to consume the structure.
    • However, with structures, there is no inheritance, and all the code to extend the string enum must be copied around--difficult if you overload more than the Equals function. The other problem is that a structure is really not necessary since all you end up doing is copying around references to the ReadOnly string values. Might as well just pass around a reference to a class.

Using the Code

The StringEnumBase provides an inheritable base that provides functionality comparable to the System.Enum class.

It has the following advantages:

  • Type checking.
  • Intellisense pop-up whenever comparison happens.
  • Custom mouse-over that indicates name and value.
  • Controlled comparison when basic operators are used. Case insensitive by default. Can make case sensitive by assigning a provided attribute to the inheriting class.
  • Can accept values outside of the named values. Can limit to named values only by assigning a provided attribute to the inheriting class.
  • Can assign a description to each named value using System.ComponentModel.Description and get it through the base class' .ToDesc function.
  • Provides all of the other relevant functions of the System.Enum class (e.g., GetValues, Parse, CompareTo, GetNames, etc.).

Inherit it in the following manner, supplying the type name of the inheritor as the generic type. This is so that the base class has a way to get the type of the class that's inheriting--used to get attributes, fields, etc. You must also declare the parameterized constructor so that it can remain private and parameterized.

Add the completionlist XML comment to get the Intellisense pop-up. The StringEnumRegisteredOnly attribute is one of the optional attributes to modify the behavior of StringEnumBase.

''' <completionlist cref="Numbers" />
<StringEnumRegisteredOnly()> _
Public Class Numbers
    Inherits StringEnumBase(Of Numbers)
    Private Sub New(ByVal StrValue As String)
        MyBase.New(StrValue)
    End Sub  
 
    ...

You can then enumerate a value as a Shared ReadOnly field or property. I prefer using a field since it fits on one line. You can also use the DescriptionAttribute in the declaration.

<Description("This is test value one.")> _
Public Shared ReadOnly One As New Numbers("ONE")

<Description("This is test value two.")> _
Public Shared ReadOnly Property Three() As Numbers
    Get 
        Return New Numbers("TWO")
    End Get
End Property 

Points of Interest

Looks like some parts of .NET are still rough, even after so many years--especially in VB.NET. For example, in DebuggerDisplayAttribute, the postfix "nq" is documented as stripping the quotes from the value that it postfixes. The only problem is that it works only for C#. It looks like the inline conditional also works only for C#.

I also stumbled across the XML comment completionlist. Why isn't this incredibly handy feature documented anywhere?

History

  • 2007-05-02 - Initial release.
  • 2007-05-22 - Revision to be more in line with the expected behavior and to add a little functionality.
    • Added more comparison operator functions to handle situations where a StringEnum is compared directly to a string or another object that can be converted to a string (including a different type of StringEnum). This allows for comparison against a string without the necessity of converting the string to the StringEnum type, which might cause an error if the StringEnum is set to accept only registered values.
    • Added a narrowing conversion to a string. The fact that it is declared narrowing is important in that other objects will be converted to StringEnum before StringEnum will be converted to a string, allowing us, in some situations, to control case-sensitivity.
    • Implemented the IConvertible interface so that StringEnum will be automatically converted to a string; like, for example, when it is passed to a function that is expecting a string argument. This is important since CType is not automatically called in every situation.
    • Modified the GetValues() function to return a typed array rather than the generic System.Array.
    • Added functions to parse the description to get the StringEnum value.
    • Added shared properties that return whether or not a particular StringEnum is using the behavior-modifying attributes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

N Jones
Web Developer
United States United States
I've been writing software for over 10 years, starting with VB 3 and working my way to VB.NET 2.0. Some of my old public work can be found at www.silverbandsystems.com (it's mostly editors for old games).
 
Currently, I'm working at a small company writing custom software in both VB.NET 2.0 and QB 4.51. (That's right, QBASIC from 1985--the year I started kindergarten.)
 
I also maintain the web site for my church (www.stmichaelwhittier.org) and I'm learning ASP.NET 2.0.

Comments and Discussions

 
GeneralAdd-on offer for more functionality PinmemberMario.Rosenbohm8-Mar-08 7:29 
Hello Nathan,
 
you StringEnum is an wonderful thing.Cool | :cool:
 
I have the Source for StringEnumBase added the follow functions:
<small><code>
 
   ''is also a copy from IsDefined
   Public Shared Function IsValueDefined(ByVal obj As Object) As Boolean
 
      Dim CmpVal As String
      Try
         CmpVal = _GetString(obj)
      Catch ex As Exception
         Throw New ArgumentException("Argument must be or resolve to a string value!", "obj", ex)
      End Try
 
      Dim NamesAndValues() As KeyValuePair(Of String, StringEnumBase(Of ThisStringEnumType)) = _GetNamesAndValues()
 
      For i As Integer = 0 To NamesAndValues.Length - 1
         If NamesAndValues(i).Value.Equals(CmpVal) Then Return True
      Next
 
      Return False
 
   End Function
 

   Public Shared Function IsDescriptionDefined(ByVal obj As Object) As Boolean
 
      Dim CmpVal As String
      Try
         CmpVal = _GetString(obj)
      Catch ex As Exception
         Throw New ArgumentException("Argument must be or resolve to a string value!", "obj", ex)
      End Try
 
      Dim FieldsAndProps() As Object = _GetFieldsAndProperties()
      Dim Desc() As DescriptionAttribute
      Dim DescType As Type = GetType(DescriptionAttribute)
 
      Dim fi As FieldInfo, pi As PropertyInfo
      For Each FOrP As Object In FieldsAndProps
         If TypeOf FOrP Is FieldInfo Then
            fi = DirectCast(FOrP, FieldInfo)
            Desc = fi.GetCustomAttributes(DescType, True)
         Else
            pi = DirectCast(FOrP, PropertyInfo)
            Desc = pi.GetCustomAttributes(DescType, True)
         End If
         If Desc.Length > 0 Then
            If (Desc(0).Description.Equals(CmpVal)) Then Return True
         End If
      Next
 
      Return False
 
   End Function
 

   Public Shared Function IsNameDefined(ByVal obj As Object) As Boolean
 
      Dim CmpVal As String
      Try
         CmpVal = _GetString(obj)
      Catch ex As Exception
         Throw New ArgumentException("Argument must be or resolve to a string value!", "obj", ex)
      End Try
 
      Dim NamesAndValues() As KeyValuePair(Of String, StringEnumBase(Of ThisStringEnumType)) = _GetNamesAndValues()
 
      For i As Integer = 0 To NamesAndValues.Length - 1
         If NamesAndValues(i).Key.Equals(CmpVal) Then Return True
      Next
 
      Return False
 
   End Function</code></small>
 
But i search problem solving for follow function offer
<small><code>
 
   ''' Returns the Enum that contains ForThisString as Description, Name or Value.
   ''' The searchorder in the attributes is first in Descriptions, second in Names, last in Values 
   Public Shared Function GetEnum(ByVal ForThisString As String) As ThisStringEnumType
      If (Not IsDescriptionDefined(ForThisString)) Then
         If (Not IsNameDefined(ForThisString)) Then
            If (Not IsValueDefined(ForThisString)) Then
               Return Nothing
            Else
               'return the Enum with this Value
               ' ... ????
            End If
         Else
            'return the Enum with this Name
               ' ... ????
         End If
      Else
         'return the Enum with this Description
               ' ... ????
      End If
   End Function</code></small>
 
Have You a litle bit time for my problem?
 
many greets
 
Mario
GeneralRe: Solution for GetEnum(String for Description or Name or Value) [modified] PinmemberMario.Rosenbohm20-Apr-08 21:55 
QuestionInteger Values Pinmemberstixoffire2-Jul-07 7:40 
AnswerRe: Integer Values PinmemberN Jones3-Jul-07 19:16 
GeneralC# Pinmemberjpbochi30-May-07 3:48 
GeneralRe: C# PinmemberN Jones15-Jun-07 6:51 
GeneralRe: C# PinmemberN Jones3-Jul-07 19:10 
GeneralRe: C# PinmemberEmanuelHaisiuc2-Jun-08 9:17 
GeneralWouldn't it be easier.... PinmemberKidan2-May-07 19:32 
GeneralRe: Wouldn't it be easier.... [modified] PinmemberN Jones3-May-07 11:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 22 May 2007
Article Copyright 2007 by N Jones
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid