Thursday, October 30, 2008

Enhancements to the Enum.Parse Method

Enum.Parse Method

The .NET Framework has an Enum class that contains two Parse methods (http://msdn.microsoft.com/en-us/library/system.enum.parse.aspx https://docs.microsoft.com/en-us/dotnet/api/system.enum.parse). Both of these methods take a string value and attempt to convert it into an Enum value. This works very well, but it has a few shortcomings.

Shortcoming 1: Enum.Parse Returns Object

The first shortcoming is that the return value from Enum.Parse is an Object. This requires the caller to typecast the result of the Enum.Parse method to the appropriate type. For example:

<FlagsAttribute()> _
Enum
Colors
    Red = 1
    Green = 2
    Blue = 4
    Yellow = 8
End
Enum

Dim
Choice As Colors

Choice =
CType([Enum].Parse(GetType(Colors), "Red"), Colors)

To get around this shortcoming I wrote the following method that will return an enumerated value of the appropriate type.

Public Shared Function ParseEnum(Of EnumType As Structure)( _
    ByVal value As String, _
    ByVal defaultValue As EnumType) As EnumType

   
Try
  

        ' Attempt to convert string to enumeration using the Parse method
        Return CType([Enum].Parse(GetType(EnumType), value, True), EnumType)

   
Catch ex As ArgumentException

       
' Return the default value
        Return defaultValue

   
End Try

End
Function

When this method is used, no typecasting is required. For example:

Dim Choice As Colors

Choice = ParseEnum(
"Red", Colors.None)

In order to use this method, the enumeration should have some default value that can be used to indicate the string could not be successfully parsed.

Shortcoming 2: Enum.Parse is unaware of the XmlEnum value

The second shortcoming of the Enum.Parse method is it does not take the XmlEnum value into account when trying to parse the string. For example, if the Colors enumeration were defined as follows:

Public Enum Colors
    Invalid
    <Xml.Serialization.XmlEnum("R")> _
    Red
    <Xml.Serialization.XmlEnum("G")> _
    Green
    <Xml.Serialization.XmlEnum("B")> _
    Blue
End Enum

And the following code was executed:

Choice = CType([Enum].Parse(GetType(Colors), "R"), Colors)

A System.ArgumentException("Requested value 'R' was not found.") would be raised to the caller. It would be nice if the Parse method would work for both the ToString and the XmlEnum values, but it only works with the ToString value.

To get around this shortcoming I wrote the following method that will return the correct enumerated value for both the ToString and the XmlEnum values.

Public Shared Function ParseEnum(Of EnumType As Structure)( _
    ByVal value As String, _
    ByVal defaultValue As EnumType) As EnumType

   
Dim Result As EnumType
    Dim ValueFound As Boolean = False

   
' Use the standard Parse method
    Try

       
' Attempt to convert string to enumeration value
        Result = (CType([Enum].Parse(GetType(EnumType), value, True), EnumType))
        ValueFound = True

   
Catch ex As ArgumentException

       
ValueFound =
False

   
End Try

   
' If that does not work, try the XmlEnum values
    If ValueFound = False Then

       
Dim Members() As System.Reflection.FieldInfo
        Dim XmlEnumAttributes() As System.Xml.Serialization.XmlEnumAttribute


       
' Get the list of Enumeration Members
        Members = GetType(EnumType).GetFields()

       
For Each Member As System.Reflection.FieldInfo In Members

           
' Only examine Enum Members
            If Member.IsSpecialName = False AndAlso _
                Member.IsLiteral = True Then

               
' Get the XmlEnum Attributes
                XmlEnumAttributes = CType(Member.GetCustomAttributes( _
                    GetType(System.Xml.Serialization.XmlEnumAttribute), True),  _
                   
System.Xml.Serialization.XmlEnumAttribute())


               
' Check the XmlEnum attribute
                If XmlEnumAttributes.Length > 0 AndAlso _
                    String.Compare(XmlEnumAttributes(0).Name, value, True, _
                    System.Globalization.CultureInfo.InvariantCulture) = 0 Then

                   
' Found Value
                   
Result = CType(Member.GetValue(Nothing), EnumType)
                    ValueFound = True
                    Exit For

               
End If ' If XmlEnumAttributes.Length > 0 AndAlso

           
End If ' If Member.IsSpecialName = False AndAlso

       
Next Member

   
End If ' If ValueFound = False Then

   
' If the value still has not been found, use the default value
    If ValueFound = False Then
        Result = defaultValue
    End If

   
' Return enumeration value
    Return Result

End
Function

When this method is used, either the ToString or the XmlEnum value can be used. For example, both of the calls below will result in Choice being set to Colors.Red:

Dim Choice As Colors

Choice = ParseEnum(
"Red", Colors.Invalid)
Choice = ParseEnum("R", Colors.Invalid)

One possible enhancement that could be made to the ParseEnum function is adding support for parsing comma-separated, XmlEnum values for bit field enumerations (e.g. “R, G” would return the value “Colors.Red Or Colors.Green”). See the FlagsAttribute Class help topic (http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute) for more details on bit field enumerations.

Visual Studio 2008/.NET Framework 3.5 Enhancement

Visual Studio 2008/.NET Framework 3.5 adds extension methods, which allow for methods to be added to existing data types without creating new derived types. This allows us to add a TryParse method, like the one shown below, to the enumerations to provide this functionality.

<System.Runtime.CompilerServices.Extension()> _
Public
Function TryParse(Of EnumType As Structure)( _ 
    ByVal enumObject As [Enum], _ 
    ByVal value As String, _ 
    ByRef result As EnumType) As Boolean 
 
    Dim ValueFound As Boolean = False 
 
    ' Use the standard Parse method 
    Try 
 
        ' Attempt to convert string to enumeration value 
        result = (CType([Enum].Parse(GetType(EnumType), value, True), EnumType)) 
        ValueFound = True 
 
    Catch ex As ArgumentException 
 
        ValueFound = False 
 
    End Try 
 
    ' If that does not work, try the XmlEnum values 
    If ValueFound = False Then 
 
        Dim Members() As System.Reflection.FieldInfo 
        Dim XmlEnumAttributes() As System.Xml.Serialization.XmlEnumAttribute 
 
        ' Get the list of Enumeration Members 
        Members = GetType(EnumType).GetFields() 
 
        For Each Member As System.Reflection.FieldInfo In Members 
 
            ' Only examine Enum Members 
            If Member.IsSpecialName = False AndAlso
                Member.IsLiteral = True Then 
 
                ' Get the XmlEnum Attributes 
                XmlEnumAttributes = CType(Member.GetCustomAttributes( _ 
                    GetType(System.Xml.Serialization.XmlEnumAttribute), True),  _
                   
System.Xml.Serialization.XmlEnumAttribute()) 
 
                ' Check the XmlEnum attribute 
                If XmlEnumAttributes.Length > 0 AndAlso
                    String.Compare(XmlEnumAttributes(0).Name, value, True, _ 
                    System.Globalization.CultureInfo.InvariantCulture) = 0 Then 
 
                    ' Found Value 
                    result = CType(Member.GetValue(Nothing), EnumType) 
                    ValueFound = True 
                    Exit For 
 
                End If ' If XmlEnumAttributes.Length > 0 AndAlso 
 
            End If ' If Member.IsSpecialName = False AndAlso 
 
        Next Member 
 
    End If ' If ValueFound = False Then 
 
    ' Indicate if the value was successfully parsed. 
    Return ValueFound 
 
End Function

I personally like this solution the best. When this method is used, either the ToString or the XmlEnum value can be used. For example, both of the calls below will result in Choice being set to Colors.Red:

Dim Choice As Colors

Choice.TryParse(
"Red", Choice)
Choice.TryParse("R", Choice)

Ideally the TryParse method would be a Shared (or static) method, but extension methods cannot be Shared (or static).

Tuesday, August 26, 2008

How to Determine If an Enumeration Is a Bit Field

In .NET you can tell the compiler to treat an enumeration as a bit field by adding a Flags attribute to its declaration. For more information about bit fields in .NET see http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspxhttps://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute. The following function will tell you if an enumeration value is a bit (or flag) field.

Function IsFlagEnum(ByVal value As [Enum]) As Boolean

    If value.GetType().IsDefined( _
       
GetType(FlagsAttribute), True) = True Then
        Return True
    Else
        Return False
    End If

End
Function


For example, given the following definitions:

<Flags()> _
Public Enum FlagEnum
    None = 0
    ValueA = 1
    ValueB = 2
    ValueC = 4
End Enum

Public
Enum RegularEnum
    Invalid = 0
    ValueA = 1
    ValueB = 2
    ValueC = 3
End Enum


IsFlagEnum(FlagEnum.ValueB) will return True, but IsFlagEnum(RegularEnum.ValueB) will return False.
 

 

Thursday, July 17, 2008

Get the XmlEnum Value for an Enum

When you serialize an enumeration (Enum in VB.NET, enum in C#), by default the XML string representation of the enumerated member is the name of the enumeration member (i.e. the same as calling the ToString function of the enumeration member). For example, if the Color property of the following object was set to Green, the XML representation of that property would be <Color>Green</Color>:

Public Enum PrimaryColor
    Invalid = 0
    Red = 1
    Green = 2
    Blue = 3
End Enum

<Serializable()> _
Public Class Sample

   
Public Property Color() As PrimaryColor
        Get
            Return mColor
        End Get
        Set(ByVal value As PrimaryColor)
            mColor = value
        End Set
    End Property
    Private mColor As PrimaryColor

End
Class

This default behavior can be modified using the XmlEnumAttribute Class. For example, if the enumeration defined above was changed as shown below, the XML representation of the Color property would be <Color>G</Color>.

Public Enum PrimaryColor
    Invalid = 0
    <System.Xml.Serialization.XmlEnum("R")> _
    Red = 1
    <System.Xml.Serialization.XmlEnum("G")> _
    Green = 2
    <System.Xml.Serialization.XmlEnum("B")> _
    Blue = 3
End Enum

Even with the new XmlEnum values applied to the enumeration members, the ToString function will still return the name of the enumeration member, not the XmlEnum value. The following function will take an enumeration value and return its XML string value (either the XmlEnum value, if present, or the ToString value):

Public Shared Function ToXmlString( _
    ByVal value As [Enum]) As String

    Dim XmlEnumAttributes() As _
        System.Xml.Serialization.XmlEnumAttribute
    Dim EnumFieldInfo As System.Reflection.FieldInfo

   
' Verify Input Arguments
    If value Is Nothing Then
        Throw New ArgumentNullException("value")
    End If

    ' Get the Field Information for the 
    '
enumeration member
    EnumFieldInfo = _ 
        value.GetType().GetField(value.ToString())

    ' Get the XmlEnum attributes for the 
    ' enumeration member
    XmlEnumAttributes = CType( _
        EnumFieldInfo.GetCustomAttributes( _
        GetType(System.Xml.Serialization.XmlEnumAttribute), True), _
        System.Xml.Serialization.XmlEnumAttribute())

    ' Check to see if an XmlEnum attribute was found
    If XmlEnumAttributes.Length < 1 Then
        ' Return the default value
        Return value.ToString()
    Else
        ' Return the XmlEnum value
        Return XmlEnumAttributes(0).Name
    End If

End Function