I was recently working on an interface to an old mainframe. I was screen scraping a terminal emulator and putting the data into objects for analysis. It was actually fun as I was using some technologies I hadn’t used yet. I wanted to use enums for my object so the intellisense would look real good. The problem is that each item translates to a character or short string on the screen. I wanted a way to keep track of the screen text that represented the enum. For an example lets use an old travel agency system for hotels. There is a screen that displays availability of a hotel. There is a screen that will display the availability of a hotel for a specific day. Each day will be either open, closed, or request. On the text screen from the mainframe these were represented as O=open, C=closed, and R=request. I created a quick enum and then I had to figure out how to associate the text from the screen to the enum in a format that is easy to read in case we need to make a change in the future (even though all these systems are going away.) Here was my first attempt
Public Enum InventoryStatusCode Open Closed OnRequest End Enum
That was easy and clear enough. Sow to associate the text on the terminal screen to the proper enum
Function GetInventoryStatusCode(ByVal ScreenText As String) As InventoryStatusCode Select Case ScreenText Case "O" Return InventoryStatusCode.Open Case "C" Return InventoryStatusCode.Closed Case "R" Return InventoryStatusCode.OnRequest Case Else Throw New ArgumentException("Screen text " & ScreenText & " not valid for Inventory Status Code") End Select End Function
Wow, is that a lot of code for so little. I am spoiled when I am using enums that can equate to meaningful numbers. If the system used 1=open, 2=closed, and 3=request then I could use the number but I couldn’t request a change to a system that is older than I am. I could use constants but I like how the enums were strongly types and grouped together so they just pop up after you hit the equal sign. Enum allows you to assign a type but it must be I searched for quite a while and found several posts about how to match a string to the enum name but I also discovered that the framework has that built in now with [Enum].Parse(GetType(InventoryStatusCode), “O”). The problem with this approach is that the enums would be InventoryStatusCode.O instead of InventoryStatusCode.Open. That tends to deflect the benefit of the readability of enums. Also, for this specific implementation some of the screen codes were special characters and blanks which would not be allowed. I finally ran across a post suggesting attributes and I like how concise it looked. There was a built in description attribute but I didn’t use that here because I might want descriptions also in the future. It also provided me the opportunity to try my hand at writing my own attribute. Hopefully it’s won’t cause confusion with the added code in this example.
Here is what I came up with. There are probably other ways. Let me know what you would do in the comments. This example supposes that you have three enum items of Open, Closed and OnRequest and you with to associate the text O,C and R with them respectively.
Public Enum InventoryStatusCode <ScreenText("O")> Open <ScreenText("C")> Closed <ScreenText("R")> OnRequest End Enum
Then I can use the helper function below. If I pass in “O” I will receive the InventoryStatusCode.Closed like below:
I can also accomplish the opposite and receive the “O” by doing the following:
That is how it looks when used. Looks pretty clear to me. We will need some more code make it all work
Here is the attribute definition:
''' <summary> ''' USed to identify the text on the terminal screen represented by an enum ''' </summary> ''' <remarks></remarks> <AttributeUsage(AttributeTargets.All)> _ Public Class ScreenText Inherits System.Attribute
‘Private fields. Private _Text As String
‘This constructor defines two required parameters: name and level. Public Sub New(ByVal Text As String) Me._Text = Text End Sub
‘Define Name property. ‘This is a read-only attribute. Public Overridable ReadOnly Property Text() As String Get Return _Text End Get End Property End Class
Here is the function so you can pass in some screen text and receive the associated enum. I couldn’t get this to work as an extension of enum which would have been nice.
''' <summary> ''' Returns the proper enumerator that matches the provided screen text ''' </summary> ''' <param name="en">Enumerator type to search</param> ''' <param name="ScreenText">Screen text to be matched to an enumerator</param> ''' <returns>Proper enumerator that matched the screen text</returns> ''' <remarks></remarks> <Extension()> _ Public Function ParseScreenText(ByVal en As Type, ByVal ScreenText As String) As [Enum] 'Dim enumitem As [Enum] For Each enumitem In [Enum].GetValues(en) If GetScreenText(enumitem) = ScreenText Then Return enumitem End If Next Return Nothing End Function End Module
Here is the extension on the enum class. Module enumExtensions ”’ <summary> ”’ Provides the attribute value ScreenText on the enumerator. ”’ The screen text is the representation of the value on a terminal screen ”’ </summary> ”’ <param name=”en”>Enumerator for which the screen text will be returned</param> ”’ <returns>String of the text representation on a terminal screen for this enumerator</returns> ”’ <remarks></remarks> <Extension()> _ Public Function GetScreenText(ByVal en As [Enum]) As String Dim type As Type = en.[GetType]() Dim memInfo As MemberInfo() = type.GetMember(en.ToString()) If memInfo IsNot Nothing AndAlso memInfo.Length > 0 Then Dim attrs As Object() = memInfo(0).GetCustomAttributes(GetType(ScreenText), False) If attrs IsNot Nothing AndAlso attrs.Length > 0 Then Return DirectCast(attrs(0), ScreenText).Text End If End If Return en.ToString() End Function End Module
Isn’t that slower?
The main disadvantage of this is that it appears to use reflection which reduces performance. It seems to me that Dynamic data uses attributes and thus reflection a lot. They may have a way to perform it once up front which reduces the impact. In my application I had seconds of wait times between commands due to legacy systems so the performance should not be a factor.
Isn’t that bad Object Oriented Design?
It’s considered bad object oriented design because “we’re asking another class to retrieve information about our enum type”. Isn’t the enum.parse doing about the same thing though? Would it be good OO if I could get the extension method to work? I’m thinking it’s worth it for this case.
Why not just make a class?
I found a post suggesting that you strongly type your domain values which accomplishes much of the same goals and is probably more performant when looking up the codes. I like the syntactic goodness of enums when coding. You don’t get this great intellisense from a class. when you hit the equals key it just lists the applicable items. When using a class it lists all the objects.
It makes it quick for coding. If someone knows how to get intellisense to automatically show you the options after hitting the equal sign then I would suggest a class. The syntax of the class is a little more verbose but is as legible in my opinion. I guess you could just remember that the type begins with “inven” and the the intellisense would pick up the rest.
In this case I feel the less verbose and clear syntax makes up for the performance and an additional helper class. I could go the the class option if performance became an issue. Tell me what you think.
I’ve done more searching while writing this post and found a whole lot more.
- C# Enum and string constants
- VB Curiosities #1: Enum, Enum, my kingdom for an Enum
- Enum description values
- Strongly type your domain values
Who thought this area was so intricate. I guess that is because enums are really syntactic sugar in a way. I think this needs more investigations. Please comment your ideas and suggestions.