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)