Welcome to CrankyGoblin.Com Sign in | Join | Help

Public Class GeoffAppleby

Inherits Microsoft.VisualBasic.MVP : Implements IBrainFart
Override Enum.ToString

I've been playing today with trying to find a way to override the default implementation of the ToString() method of an Enum.  So far I've had to no luck, but i've come pretty close.

I did a bit of research, and no one (that I've found) has figured out a way to do it (I don't want to say impossible, for surely NOTHING is impossible is it? Not so far, only things that are very hard :) All examples people talk about is to not use the ToString method, but to either a) call your own defined function that is hard-coded to input/output specific string, or b) call your own defined function that uses reflection to read custom attributes of the enum members, and return those strings.

These are both fine ways of doing it (at least, b) is, a) doesn't stand up over time) but i wanted something a little bit better than that.

I was hoping that, like with a lot of classes, the ToString method could be, of not overridden, circumvented by using a type converter. This has worked out as a fairly good way of doing it, but you still can't call the ToString method directly.

However, I thought I'd post my solution so far as to a very generic way of 'prettying up' enum strings.

Normally, we declare an enum like this:

Public Enum MyEnum

  ValueOne = 0

  ValueTwo = 1

  ValueThree

End Enum

And what i've done is add one attribute to each enum that I want to override.

<System.ComponentModel.TypeConverter(GetType(GenericEnumConverter))> _

Public Enum MyEnum

  ValueOne = 0

  ValueTwo = 1

  ValueThree

End Enum

And now we need to implement the GenericEnumConverter class:

'GenericEnumConverter class.

'The constructor recieves the System.Type of the Enum in question.

'and bases all it's work on the fact that it's dealing with that

'type. The good part here is that there's absolutely no hard-coding

'of any particular defined Enum.

Public Class GenericEnumConverter

  'Inherit EnumConverter to deal with the cases we don't

  'explicitly want to worry about.

  Inherits System.ComponentModel.EnumConverter

 

  'keep a record of the System.Type of the Enum we'll be dealing with.

  Private moEnumType As System.Type

 

  Public Sub New(ByVal poEnumType As System.Type)

    MyBase.New(poEnumType)

    'keep this type for later.

    moEnumType = poEnumType

  End Sub

 

  'This function is called to check what data type we can convert to.

  'We really want to convert to our enum type.

  Public Overloads Overrides Function CanConvertTo( _

    ByVal context As System.ComponentModel.ITypeDescriptorContext, _

    ByVal destinationType As System.Type) As Boolean

 

    If destinationType.Equals(moEnumType) Then

      Return True

    End If

    Return MyBase.CanConvertTo(context, destinationType)

 

  End Function

 

  'This is called to check what we can convert from.

  'We can convert from String, thanks.

  Public Overloads Overrides Function CanConvertFrom( _

  ByVal context As System.ComponentModel.ITypeDescriptorContext, _

  ByVal sourceType As System.Type) As Boolean

 

    If sourceType.Equals(GetType(System.String)) Then

      Return True

    End If

    Return MyBase.CanConvertFrom(context, sourceType)

 

  End Function

 

  'This requests a converstion.

  Public Overloads Overrides Function ConvertTo( _

  ByVal context As System.ComponentModel.ITypeDescriptorContext, _

  ByVal culture As System.Globalization.CultureInfo, _

  ByVal value As Object, _

  ByVal destinationType As System.Type) As Object

 

    'cover all bases. If the destination is the enum, then

    'we go one way. if it's a string, then we go the other

    Try

      If destinationType.Equals(moEnumType) Then

        Return StringToEnum(CStr(value))

      ElseIf destinationType.Equals(GetType(System.String)) Then

        Return EnumToString(value)

      End If

    Catch ex As Exception

      'if an exception gets thrown, then something went wrong.

      'at this stage give up and fall through to the mybase call.

    End Try

    Return MyBase.ConvertTo(context, culture, value, destinationType)

 

  End Function

 

  'This also requests a converstion.

  Public Overloads Overrides Function ConvertFrom( _

  ByVal context As System.ComponentModel.ITypeDescriptorContext, _

  ByVal culture As System.Globalization.CultureInfo, _

  ByVal value As Object) As Object

 

    'cover all bases. If the destination is the enum, then

    'we go one way. if it's a string, then we go the other

    Try

      If value.GetType.Equals(GetType(System.String)) Then

        Return StringToEnum(CStr(value))

      ElseIf value.GetType.Equals(moEnumType) Then

        Return EnumToString(value)

      End If

    Catch ex As Exception

      'if an exception gets thrown, then something went wrong.

      'at this stage give up and fall through to the mybase call.

    End Try

    Return MyBase.ConvertFrom(context, culture, value)

  End Function

 

  'given the passed in enum, convert to a string,

  'but inject spaces in font of all capital letters.

  'a regex might work best here, but for now, let's do it the long way

  Private Function EnumToString(ByVal poSource As Object) As String

 

    'convert the value to a string

    Dim sVal As String = System.Enum.GetName(moEnumType, poSource)

    Dim sNew As New System.Text.StringBuilder

 

    'loop through each char, looking for spaces.

    For i As Int32 = 0 To sVal.Length - 1

      If i <> 0 AndAlso System.Char.IsUpper(sVal.Chars(i)) Then

        sNew.Append(" "c)

      End If

      sNew.Append(sVal.Chars(i))

    Next

    Return sNew.ToString

  End Function

 

  'given the passed in string, convert to the right enum type.

  Private Function StringToEnum(ByVal psSource As String) As Object

 

    'first, remove the spaces that were inserted above

    Dim sNewVal As String = psSource.Replace(" ", "")

 

    'if this space-less string maps to an enum value, convert it.

    If System.Enum.IsDefined(moEnumType, sNewVal) Then

      Return System.Enum.Parse(moEnumType, sNewVal)

    End If

    'damit, can't do it. Throw an exception so we can fall

    'into the base class

    Throw New Exception("Can't work it out, I give up!")

 

  End Function

 

End Class

This isn't perfect, but it's fairly robust. In my opinion anyway.

The trick now is to get it invoked. ToString, as I said, won't work still. We need to get the TypeConverter invoked. Here's how I do it:

'get the type converter. If a type converter is defined for this enum,

'it will be returned. If not, the standard EnumConverter will be returned

'either way, the following code will be safe.

Dim oConv As System.ComponentModel.TypeConverter = _

  System.ComponentModel.TypeDescriptor.GetConverter(GetType(MyEnum))

 

'convert 'ValueThree' to a string.

sString = oConv.ConvertToString(eMyEnum)

'sString now contains 'Value Three'

 

'convert the string back to an enum

eNewEnum = CType(oConv.ConvertTo(sString, GetType(MyEnum)), MyEnum)

'eNewEnum = MyEnum.ValueThree

It's a tiny bit messier than just calling eMyEnum.ToString(), but the beauty of it (again, in my opinion) is that it's nice and generic and pretty much garanteed to work every time. And it doesn't depend on explicit reflection calls. If the GenericEnumConverter hasn't been defined as the typeconverter for a class, then .net will fall through to the standard. Yay.

Anyone got any suggestions, improvements, or actually found a way to override the ToString method of an Enum?

Update: I just did a little test. I made a test class that included a property of type MyEnum, then loaded an instance of that class into a propertygrid. The dropdown was automatically populated with the pretty text strings. Sweet!!!

Second Update: I just tested what happens when you dump that (aforementioned) test class through an XMLSerializer. No dice...but i don't think that matters. The point of doing this is for display purposes. :)

Listening to: there goes the neighbourhood - body count - (5:50)
Posted: Thursday, 18 November 2004 12:10 PM by Geoff Appleby

Comments

Eric Newton said:

Fascinating... you've brought up an annoyance of mine too, that enums are a little too much like Integers...

I frequently "cheat" the reflection apis by doing this:
<code>
Type t = typeof(System.Web.UI.Page);
t.GetMethod("someprivateMethod", (BindingFlags)65535);
</code>
notice how you can cast 65535 to be a BindingFlags type, and will assume a value of 65535 which isnt really defined...

but yet you cant add a few "helper" methods to an enum:

Since enums are value types, I would propose being able to add static methods to an enum.

first example would be to parse something:

<code>
enum DatabaseObjectType
{
Table=1,
View=2,
Procedure=3,
Function=4

public static DatabaseObjectType Parse(string value)
{
switch(value.Trim())
{
case "P": return DatabaseObjectType.Procedure;
case "U": return DatabaseObjectType.Table;
//and so forth
}
}
}
</code>
# November 19, 2004 8:52 AM

Ross said:

The easier way would be to attribute each value in the enum with an alternative textual representation. See the following link, towards the bottom: "Extending the humble enum"

http://www.dotnetconsult.co.uk/weblog/CategoryView.aspx/.NET

Regards,

Ross
# December 22, 2004 3:56 PM

Public Class GeoffAppleby said:

I'm sure you all know how to use Enums. But have you ever tried to take it just that little bit further?...
# May 25, 2005 5:17 AM

Public Class GeoffAppleby said:

The work on the next TechEd here in Oz has already started, even though it's still 10 months away. Still,...
# October 21, 2005 8:50 AM

break_r said:

One possible solution (much simpler I think):

public enum FontName
{
Arial,
Courier_New,
Georgia,
Microsoft_Sans_Serif,
Times_New_Roman
}

// writes "Courier New" Console.WriteLine(FontName.Courier_New.ToString().Replace('_', ' '));
# March 3, 2006 12:20 AM

Geoff Appleby said:

Ewww! Nooooooo! :)

Yes, it's simpler, but you go against the coding standards for defining enums. Where in the .net framework have you seen any enum values that have underscores?

In Krzystos Cwalina and Brad Abrams book "Framework Design Guildelines", for example, they explicitly state that enum values should be Pascal Cased (p 36). And I heartily agree for, for both internal enums and public ones (which is all the book covers).

Also, with a small amount of work, the typeconverter solution will also make values rendered in dropdowns in things like the property grid or databinding show the nice versions of the enum values too.

You're right, it's much simpler, but calling it directly myself is not all I was trying to acheive :)
# March 3, 2006 12:35 AM

Public Class GeoffAppleby said:

People who read my blog know that I'm happiest coding in the back end, or in places where things like...
# March 5, 2006 10:24 PM

Public Class GeoffAppleby said:

Author: &lt;a href=&quot;blogs/geoff.appleby&quot;&gt;Geoff Appleby&lt;/a&gt;&lt;br /&gt;I thought I'd go through an example of how to get user based settings from My.Settings displayed nicely in the propertygrid - and by nicely I mean with all the bells and whistles :) Sorry C# guys, I'm dealing with the My namespace from VB. To be fair though. My.Settings is only a small part of it. To do this, I'm going to need to pull together a few different things that I've blogged about before.
# March 6, 2006 8:55 AM

Ana said:

I'am working on a layered solution. The enums are in the Datalayer exposed by a Web Service that is access by the UI.

But I can´t get to work the "TypeDescriptor.GetConverter(value.GetType())" to use mi own. I already added the atribbute "[TypeConverter(typeof(LocalizedEnumConverter))]" to the enum.

Could you help me please.

# March 23, 2010 8:22 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

To submit your comment, click on these pictures:
  • Sleepy Geoff
  • Geoff's big sister's tongue
  • Geoff with sunglasses
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Can't recognise the people in these pictures? Look here for a quick introduction.
There's a time limit for you to get your comment submitted before this set of pictures expires. If you think it's been longer than 10 minutes, get some new pictures first (you won't lose what you've typed so far).
Get some new pictures 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS