Link to home
Start Free TrialLog in
Avatar of neelbak
neelbak

asked on

What is the best way to combine an enum with a string value

Below are 2 functions that I have associated with an enum and it's value.  What is the best way to combine these items into one object type to give me the following functionality.  I basically want to be able to pass an enum value as parameters but parse and output the Textual description of the enum.

I'm open to suggestions on the best way to handle this.  I have about 20 different enums and want to have each enum's functions contained.

' Looking for behavior similar to this
Dim x as GenderType             ' No instantiation necessary
x = Gender.Male                    ' setting takes a enum value
x.ToString()                          '  to string returns a string value of the current enum
x.Parse("Male")                     ' sets x to Gender.Male



Public enum Gender
   NotSpecified = 0
   Male = 1
   Female = 2
end enum

Public Function ToString_Gender(ByVal val as Gender) as String
   select case val
      case Gender.NotSpecified
             return "Not Specified"
      case Gender.Male
             return "Male"
      case Gender.Female"
             return "Female"
      case else
             return String.empty
       end select
end function

Public Function Parse_Gender(ByVal text as string) as Gender
   select case "Not Specified"
           return Gender.NotSpecified
   select case "Male"
           return Gender.Male
   select case "Female"
          return Gender.Female
   case else
         return nothing
    end select
end function
Avatar of MaitreMind
MaitreMind

Try this it is completely generic:

Public Enum Gender
   NotSpecified = 0
   Male = 1
   Female = 2
End Enum

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   Dim g1, g2 As Gender
   Dim sg1 As String

   g1 = Gender.Male
   sg1 = Type_ToString(g1)

   g2 = DirectCast(Type_Parse(sg1, g2), Gender)
End Sub

Public Function Type_ToString(ByVal myEnum As System.Enum) As String
   'This method is not necessary since it is only a single line of code
   Return [Enum].GetName(myEnum.GetType, myEnum)
End Function

Public Function Type_Parse(ByVal text As String, ByVal myEnum As System.Enum) As System.Enum
   Dim val As System.Enum

   For Each val In [Enum].GetValues(myEnum.GetType)
      If val.ToString.ToUpper = text.ToUpper Then
         Return val
      End If
   Next

   Return myEnum
End Function
This bit of code will allow you to have the enum be different then it’s description. Also it keeps the description of the enum value right with the enum declaration, which in my book is a good idea.


Your enum: (this enum will work the same as a normal enum, notice you can type what ever for the description)
    Public Enum Gender
        <System.ComponentModel.Description("Not Specified")> NotSpecified = 0
        <System.ComponentModel.Description("Male")> Male = 1
        <System.ComponentModel.Description("Female")> Female = 2
    End Enum

The code: (don't some complex reflection I know, but I tested it)
    Public Function EnumToDescription(ByVal Value As [Enum]) As String
        'get the type of the enum
        Dim type As System.Type = Value.GetType

        'iterate over all the members of this type
        For Each member As Reflection.MemberInfo In type.GetMembers(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
            If member.MemberType = Reflection.MemberTypes.Field Then
                'test to see if this field equals the given value
                If System.Enum.ToObject(Value.GetType, Value).Equals(DirectCast(member, Reflection.FieldInfo).GetValue(Nothing)) Then
                    'now look for the description attribute on this member
                    For Each att As Object In member.GetCustomAttributes(False)
                        If TypeOf att Is System.ComponentModel.DescriptionAttribute Then
                            'we found the description, return it
                            Return DirectCast(att, System.ComponentModel.DescriptionAttribute).Description
                        End If
                    Next
                End If
            End If
        Next

        'after all this work we could not find anything that matches
        Return ""
    End Function
    Public Function DescriptionToEnum(ByVal EnumType As Type, ByVal Description As String) As [Enum]
        'iterate over all the members of this type
        For Each member As Reflection.MemberInfo In EnumType.GetMembers(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
            If member.MemberType = Reflection.MemberTypes.Field Then
                'now look for the description attribute on this member
                For Each att As Object In member.GetCustomAttributes(False)
                    If TypeOf att Is System.ComponentModel.DescriptionAttribute Then
                        'we found the description, compair it to what we were given
                        If DirectCast(att, System.ComponentModel.DescriptionAttribute).Description = Description Then
                            'we want to return this value
                            Return DirectCast(DirectCast(member, Reflection.FieldInfo).GetValue(Nothing), [Enum])
                        End If
                    End If
                Next
            End If
        Next

        'could not find a description matching in this enum type
        Return DirectCast(Nothing, [Enum])
    End Function


The test drive: (try this out to see the above functions in action)
'the code below goes from enum value to that enums description then from a description back to the enum.
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim Des As String = EnumToDescription(Gender.NotSpecified)
        Dim Gen As Gender = DirectCast(DescriptionToEnum(GetType(Gender), Des), Gender)

        MsgBox(Des & vbCrLf & Gen.ToString & vbCrLf & CInt(System.Enum.ToObject(GetType(Gender), Gen)))

    End Sub



One possible down side I see to this code is that you have to know what type of enum the description is from to parse from description to enum. Also there is a ton of directcasting, which may or may not suit you.
Xersoft,

You are doing the 'exact' same thing I did with 3x as much code and complexity.

Simplify, don't over engineer!

MaitreMind
MaitreMind,

Did you notice his description for NotSpecified is 'Not Specified' with a space?

I was unable to get your code to allow this sort of description. Also what if I wanted to have a description for an enum value that was an entire sentence?

I may be wrong but I'm not seeing how your code allows a description of anything other then the enum value text.

I'm sorry if I'm missing something in your code.

Also I'm not sure if this is something neelbak needs or not...
Avatar of neelbak

ASKER

xersoft is  on the right track.  I do need to separate out the description of the enum value to allow for full setences or spaces.  MaitreMind, your example just gets me the name of the enum but doesn't allow for a complete description.

xersoft, your code works for doing this.  The only thing is that I would like to encapsulate this  into a object to have the same type of functionality as a base type such as "Integer".

For example

Dim y as integer
y = Integer.Parse("23")
y.ToString()                         'Outputs 23
y = 23

So like I said in my orginal question.  How do I get that functionality into my object type?

' Looking for behavior similar to this
Dim x as GenderType             ' No instantiation necessary
x = Gender.Male                    ' setting takes a enum value
x.ToString()                          '  to string returns a string value of the current enum
x.Parse("Male")                     ' sets x to Gender.Male
x = GenderType.Parse("Male")    ' or this for parsing

Avatar of neelbak

ASKER

This is starting to fustrate me so I'm raising the stakes with some extra points. :-)
Are you using VB2005 by chance? Or would you use c# for this?
You could override the = operator and get this functionality I think.

Pre 2005 VB won’t allow this though.
Avatar of neelbak

ASKER

I'm using VB2005
Here is a class I came up with that I think might fit your need.

Public Class Gender
    Private mvalue As Integer


    Private Shared mNotSpecified As Gender = New Gender(0)
    Private Shared mMale As Gender = New Gender(1)
    Private Shared mFemale As Gender = New Gender(2)
    <System.ComponentModel.Description("Not Specified")> Public Shared ReadOnly Property NotSpecified() As Gender
        Get
            Return mNotSpecified
        End Get
    End Property
    <System.ComponentModel.Description("Male")> Public Shared ReadOnly Property Male() As Gender
        Get
            Return mMale
        End Get
    End Property
    <System.ComponentModel.Description("Female")> Public Shared ReadOnly Property Female() As Gender
        Get
            Return mFemale
        End Get
    End Property

#Region "Description"
    Private Function GenderToDescription(ByVal Value As Integer) As String
        'get the type of the enum
        Dim type As System.Type = Me.GetType

        'iterate over all the members of this type
        For Each member As Reflection.MemberInfo In type.GetMembers(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
            If member.MemberType = Reflection.MemberTypes.Property Then
                'test to see if this field equals the given value
                If DirectCast(DirectCast(member, Reflection.PropertyInfo).GetValue(Nothing, Nothing), Gender).mvalue = Me.mvalue Then
                    'now look for the description attribute on this member
                    For Each att As Object In member.GetCustomAttributes(False)
                        If TypeOf att Is System.ComponentModel.DescriptionAttribute Then
                            'we found the description, return it
                            Return DirectCast(att, System.ComponentModel.DescriptionAttribute).Description
                        End If
                    Next
                End If
            End If
        Next

        'after all this work we could not find anything that matches
        Return ""
    End Function
    Private Shared Function DescriptionToGender(ByVal Description As String) As Gender

        'iterate over all the members of this type
        For Each member As Reflection.MemberInfo In GetType(Gender).GetMembers(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static)
            If member.MemberType = Reflection.MemberTypes.Property Then
                'now look for the description attribute on this member
                For Each att As Object In member.GetCustomAttributes(False)
                    If TypeOf att Is System.ComponentModel.DescriptionAttribute Then
                        'we found the description, compair it to what we were given
                        If DirectCast(att, System.ComponentModel.DescriptionAttribute).Description = Description Then
                            'we want to return this value
                            Return DirectCast(DirectCast(member, Reflection.PropertyInfo).GetValue(Nothing, Nothing), Gender)
                        End If
                    End If
                Next
            End If
        Next

        'could not find a description matching in this enum type
        Return mNotSpecified
    End Function

#End Region


    Private Sub New(ByVal Value As Integer)
        Me.mvalue = Value
    End Sub
    Public Sub New()
        mvalue = 0
    End Sub

    Public Overrides Function ToString() As String
        Return Me.GenderToDescription(mvalue)
    End Function
    Public Shared Function Parse(ByVal Description As String) As Gender
        Return DescriptionToGender(Description)
    End Function



    Public Shared Operator =(ByVal g1 As Gender, ByVal g2 As Gender) As Boolean
        If g1.mvalue = g2.mvalue Then Return True Else Return False
    End Operator
    Public Shared Operator <>(ByVal g1 As Gender, ByVal g2 As Gender) As Boolean
        If g1.mvalue <> g2.mvalue Then Return True Else Return False
    End Operator
End Class
I tested it with this, which I got from your post above:


        Dim x As Gender

        x = Gender.Male
        x.ToString()
        x = Gender.Parse("Male")

        Dim y As Gender = Gender.Female

        MsgBox(y = x)

        x = y

        MsgBox(x.ToString)

        MsgBox(x = y)
Dim x as GenderType             ' No instantiation necessary
x = Gender.Male                    ' setting takes a enum value
x.ToString()                          '  to string returns a string value of the current enum
x.Parse("Male")                     ' sets x to Gender.Male
x = GenderType.Parse("Male")    ' or this for parsing


From your above list:

My idea requires instantiation i.e. Dim x as new GenderType
My idea does not allow the x.Parse(“Male”) to set x, you need to use x = Gender.Parse(“Male”)

Hope my ideas help.
ASKER CERTIFIED SOLUTION
Avatar of xersoft
xersoft

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
MaitreMind,

I’m all for keeping it simple but that just does not give him the functionality he needs, or at least that I understand he needs.

I’m understanding, neelbak, that you want to place this functionality into the enum itself?

Plus I imagine it would get pretty hard to describe an enum value with more then a few words.

I guess it all boils down to what is needed. If it’s worth creating complex code to get exactly what is needed, or is simpler code alright for your purposes.

I have to admit I see that appeal of placing the parse and tostring methods right in the enumeration.

Plus what if one wanted to offer other methods that deal specifically with that enum? One could add a method called GetMateType to the structure I provided and based on the current value value of the enum i.e. Male, Female a proper type could be returned. Now maybe that does not make much sense for this example but what about more complex examples?

Public enum FishTypes
      FishA
      FishB
      FishC
      FishD
      FishE
      FishF
End enum


Now say a method is needed to see all the types of fish one other specific type of fish eats.

Dim aFish as FishTypes = FishTypes.FishA
For each Fish as FishTypes in aFish.GetFoodTypes()
      Msgbox Fish.ToString
Next

Define a GetFoodTypes method and return an array of FishTypes objects
Sure another possibly crazy example but hopefully you get the idea. Placing the GetFoodTypes method right in the enum makes sense and makes working with those enums more intuitive.

Sorry if I’m way off base here with these ideas.
Here is a way to encapsulate it into your own structure

Note it would to be able to have a Default property so you could do this:
   myGender = mygender.Not_Specified

VB.Net doesn't allow it:
   Properties with no required parameters cannot be declared 'Default'.

Yet you can do this instead:
   myGender.Set = myGender.Get.Not_Specified


   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      Dim myGender As GenderStruct
      Dim g1 As Gender
      Dim sg1 As String

      myGender.Set = Gender.Not_Specified
      sg1 = myGender.ToString

      g1 = myGender.Parse(sg1)
   End Sub

   Public Enum Gender
      Not_Specified = 0
      Male = 1
      Female = 2
   End Enum

   'Note: All you would need to do in addition is add the any extra enum functionality GetType() etc. to the structure
   Public Structure GenderStruct
      Private _Gender As Gender

      Public ReadOnly Property [Get]() As Gender
         Get
            Return _Gender
         End Get
      End Property

      Public WriteOnly Property [Set]() As Gender
         Set(ByVal Value As Gender)
            _Gender = Value
         End Set
      End Property

      Public Shadows Function ToString() As String
         Return _Gender.ToString.Replace("_", " ")
      End Function

      Public Function Parse(ByVal text As String) As Gender
         Dim val As Gender

         For Each val In [Enum].GetValues(_Gender.GetType)
            If val.ToString.ToUpper = text.Replace(" ", "_").ToUpper Then
               Return val
            End If
         Next

         Return Nothing
      End Function

   End Structure


Keep it Simple ...

MaitreMind
I don't understand why

x.Set = Gender.Female

is simpler then

x = Gender.Femail

as my examples allow one to do?


Is it the amout of code that troubles you MaitreMind? It's not so much more code, no hundreds of lines longer...
one benefit I see to your code MaitreMind:

Dim x As GenderStruct
x.set = [brings up a list of posable values]

in my example
dim x as Gender
x = Gender. [Brings up a list of possable values]

you have to type out that word Gender everytime. That might hurt more with longer named enums.
Avatar of neelbak

ASKER

Below is code that should hopefully explain a bit better my need for this type of structure.

MaitreMind, I agree with keeping things simple.  However, your approach does not suit my particular needs.  These "enum"s will eventually contain more information and functionality than just a simple word conversion.  Also xersoft is right in that the description may be long and I wouldn't want the enum to be that lengthy.

xersoft, I've modified your code a bit to something that seems to work.  I would appreciate any comments on this approach.  I did this to hopefully simplify things a bit more for myself.  What is the benifit of using the Description attribute over an array of strings as I have below? I also am still deciding between a structure and class approach.  Any thoughts on the matter?

Another thing is that when I assign a value to my variable below.  Is there a way to get the Intellisense tooltip to automatically give me a list of possible values so that I don't have to do "Gender."?  For example, when you use a boolean "True" and "False" automatically show up.


Dim x as Gender
x  = Gender.NotSpecified


Public Structure Gender

#Region "Variables"
    Private mvalue As Integer

    Private Shared mNotSpecified As Gender = New Gender(0)
    Private Shared mMale As Gender = New Gender(1)
    Private Shared mFemale As Gender = New Gender(2)

    'Contains descriptive text describing each each value
    Private Shared aDescription() As String = New String() {"Gender has not been specified.", "Gender is male.", "Gender is female."}

    'Contains value of object in format used for in third party device function.
    Private Shared aDeviceValue() As String = New String() {"NONE", "MALE", "FEMALE"}
#End Region

#Region "Public Property Values"

    Public Shared ReadOnly Property NotSpecified() As Gender
        Get
            Return mNotSpecified
        End Get
    End Property

    Public Shared ReadOnly Property Male() As Gender
        Get
            Return mMale
        End Get
    End Property

    Public Shared ReadOnly Property Female() As Gender
        Get
            Return mFemale
        End Get
    End Property

#End Region

#Region "Conversion Functions"

    'Converts "enum" value to a english descriptive text
    Private Function EnumToDescription(ByVal Value As Integer) As String
        If Value >= LBound(aDescription) And Value <= UBound(aDescription) Then
            Return aDescription(Value)
        Else
            Return String.Empty
        End If

    End Function

    'Converts "enum" value to value used for third party function
    Private Function EnumToDevice(ByVal Value As Integer) As String
        If Value >= LBound(aDeviceValue) And Value <= UBound(aDeviceValue) Then
            Return aDeviceValue(Value)
        Else
            Return String.Empty
        End If
    End Function

    'Converts value from third party function to "enum" value
    Private Shared Function DeviceToEnum(ByVal TranslatedValue As String) As Gender
        For i As Integer = LBound(aDeviceValue) To UBound(aDeviceValue)
            If aDeviceValue(i) = TranslatedValue Then
                Return New Gender(i)
            End If
        Next

        'could not find a description matching in this enum type
        Return mNotSpecified
    End Function

#End Region

#Region "Public Functions"

    'Constuctor
    Private Sub New(ByVal Value As Integer)
        Me.mvalue = Value
    End Sub

    'Outputs to value used in third party plugin
    Public Function ToDeviceValue() As String
        Return Me.EnumToDevice(mvalue)
    End Function

    'Outputs a description of the value
    Public Overrides Function ToString() As String
        Return Me.EnumToDescription(mvalue)
    End Function

    'Parse input from third party plugin to convert to "enum" value
    Public Shared Function Parse(ByVal TranslatedValue As String) As Gender
        Return DeviceToEnum(TranslatedValue)
    End Function

    Public Shared Operator =(ByVal g1 As Gender, ByVal g2 As Gender) As Boolean
        If g1.mvalue = g2.mvalue Then Return True Else Return False
    End Operator

    Public Shared Operator <>(ByVal g1 As Gender, ByVal g2 As Gender) As Boolean
        If g1.mvalue <> g2.mvalue Then Return True Else Return False
    End Operator

#End Region
   
End Structure
What is the benifit of using the Description attribute over an array of strings as I have below?
-One benefit using the array over the attribute is that it’s probably faster. Reflection operations are slow. However one benefit of using the attribute description over the array is that the description is very closely coupled with the thing it is describing. I think either approach will work; I’d go with the attribute personally.


I also am still deciding between a structure and class approach.  Any thoughts on the matter?
-I’d use a structure because you don’t have to use the new keyword to make a new instance of the object before you can use it example:
Dim x as Gender
Msgbox x.tostring

If you run those two lines in order the outcome will be different if Gender is a class. If Gender is a class the code will crash and say null reference exception. If you use a structure the same code will still work. With careful coding either a class or structure will work. I’d go with a structure personally.


Another thing is that when I assign a value to my variable below.  Is there a way to get the Intellisense tooltip to automatically give me a list of possible values so that I don't have to do "Gender."?  For example, when you use a boolean "True" and "False" automatically show up.
This one I have no idea about, short of using MaitreMind’s idea, which may or may not suit your needs for this project.
Enums should not be long.

I use enums for loading fixed Dropdowns and the values are short.

What will you be doing with the class? Are you passing it around between tiers, keep in mind classes are reference types and structures are value types.

I create structures load them up and pass them between layers as MarshalByRefObjects so I get copies of the objects passes versus references to objects constantly marshalled.

If you put properties on the class for each Enum value you lose the purpose of the Enum with the Get and Set Properties exposed you get the enum choices. You also get the value by Intellisense, but if the enum text is too long it gets ridiculous.

MaitreMind
Avatar of neelbak

ASKER

Thanks to both of you for your help.  I ended up splitting the points between the two of you for the following reasons.

I ended up going with using separate functions for parsing and converting the enum to a string and using a standard enum to pass around in my functions.  I really wanted to keep things into a nice organized class, but unfortunately, after

Xersoft, you provided a solution that was close to what I orignally asked.  It is extremely helpful but maybe too much.

MaitreMind, you fought hard and after that you've convinced me to keep things simple.  Passing around this more object may be too much overhead.

Thanks to both of you.