Welcome to CrankyGoblin.Com Sign in | Join | Help

Public Class GeoffAppleby

Inherits Microsoft.VisualBasic.MVP : Implements IBrainFart
A Little Enum Tip

I'm sure you all know how to use Enums. But have you ever tried to take it just that little bit further?

I've played a little bit, as I've posted before.

Last night, I had an interesting situation where a stored procedure was returning me an Integer value return code, which was supposed to map to a correct enum value. The twist here is that the database development is done in parallel to the code development, by a different team. We work closely together, but sometimes accidents happen, and things get a little bit out of whack.

Now, one thing that is always good to do is to do a verification that the value you have is a legitimate value - in this case, we want to be sure that it really does map into the enum it's supposed to match. The code, before I tweaked it, was not very well optimised. Sure, the execution path was always pretty straightforward, but it was an ugly method...a sample might help.

    Private Function IsValid(ByVal retCode As Int32) As MyEnum

        Select Case retCode
            Case MyEnum.Val1
                Return MyEnum.Val1
            Case MyEnum.Val2
                Return MyEnum.Val2
            Case MyEnum.Val3
                Return MyEnum.Val3
        End Select

        Return MyEnum.Failed

    End Function

This is a really bad example, but it gets the point across (this is AirCode!). But the method I was fixing (which I didn't write!) did something along these lines (there was a bit more logic involved, for example). Oh, and the Enum actually had about 25 or 30 values, so the Case statement was huge.

I hate seeing things like this. If your case statement gets more than 4 or 5 cases long, perhaps there's a completely different approach you need to try. In this case, there's no room to grow. Creating a new value in the enum means updating the enum _and_ this function.

So I worked a little magic, and reduced it down to this little sucker:

    Private Function IsValid(ByVal retCode As Int32) As MyEnum

        If System.Enum.IsDefined(GetType(MyEnum), retCode) Then
            Return CType(retCode, MyEnum)
        End If

        Return MyEnum.Failed

    End Function

This is a little better. Now, if we go and add more values to the Enum, this function needs be none the wiser. However, there's still more future proofing that can be done.

What if the enum gets changed to be a a Flags enum? A flags enum is one where you define your enum values in such a way that you can use bitwise operations on them, to hold more than one value at once. You generally do this by defining your values as exact powers of two (1, 2, 4, 8, 16 etc) and adding an FlagsAttribute attribute to the declaration of the enum.

Does a flags enum work with my latest method? Yes and no.

Yes, it does when you've got exact value matches. But it doesn't if you pass in a combined value where more than one enum value has been ORd together.

So if we modify the enum to look like this:

<Flags()> _
Public Enum MyEnum
    Failed = 0
    Val1 = 1
    Val2 = 2
    Val3 = 4
End Enum

Then we want the call to validate the value to work when we pass in 3, 5, 6 and 7 (as well as 0, 1, 2, and 4) but to fail when we pass in 8 or above. Of course, a bit of help from reflection was the key. Since we really do want to use reflection as little as possible, I wrote it in such a way that it only used reflection if simple tests failed first.

Here's my final result, future proofing even further by not even hard-coding to a specific enum type, but accepting any.

    Public Function IsValid(ByVal poType As System.Type, ByVal plValue As Int32) As Boolean
        Dim bRet As Boolean = System.Enum.IsDefined(poType, plValue)
        If bRet = False Then
            If poType.GetCustomAttributes(GetType(System.FlagsAttribute), False).Length > 0 Then
                Dim lRet As Int32 = SumValues(poType)
                If (plValue And lRet) = plValue Then
                    bRet = True
                End If
            End If
        End If
        Return bRet
    End Function

    Private Function SumValues(ByVal poType As System.Type) As Int32
        Dim lRet As Int32 = 0
        For Each lVal As Int32 In System.Enum.GetValues(poType)
            lRet += lVal
        Next
        Return lRet
    End Function

First, do the simple test. By my careful calculations a good chunk of the majority of the time (that's correct to within 3 decimal places) the value passed in will be of a simple nature, and the call to System.Enum.IsDefined() will return true.

Next, if the first test failed, then it's time to get serious. We test for the FlagsAttribute attribute on the type, to ensure that they really are a Flags enum - if not, then we have to give up and say without a doubt that the value is not a member of the enum.

Having successfully tested for the existence of the FlagsAttribute, we then get the sum of all values defined in the enum. The call to System.Enum.GetValues() in the SumValues() method returns an array of all values defined, and we simply sum them up.

If you AND a value with the sum of all allowed values and get your original value back as a result, then you have a perfect match, and all flags set within the value are valid. Yay!

This method now also simply returns a Boolean response. If it returns true, we know it's safe to cast the integer without an exception being thrown:

        Dim eVal As MyEnum
        Dim lVal As Int32 = 3
        If IsValid(GetType(MyEnum), 3) Then
            eVal = CType(lVal, MyEnum)
        End If

Pretty cool huh?

There's actually at least a couple of bugs still in there. I've fixed it in mine, but I thought I'd leave it open for people to fight over. Any suggestions on what else to do to the IsValid() method to ensure that there's no exceptions thrown?

Listening to: nothing compares to you - me first and the gimme gimmes - (2:39)
Posted: Wednesday, May 25, 2005 5:14 AM by Geoff Appleby
Filed under:

Comments

Darrell Norton's Blog [MVP] said:

Geoff wrote a whole bunch of code for dealing with Enums. Enums are annoying because you can cast any...
# May 25, 2005 7:38 AM

Bill McCarthy said:

I feel so dirty, so used ;) That's what you get I s'pose when you let any "type" in without checking that are the right "type"'s
# May 25, 2005 6:58 PM

Geoff Appleby said:

Hey Bill,

Yup, that was my thought too - who's to say the type passed in is actually an enum type at all?

Oh, and by commenting here you've reminded me of something. When I click the post button on this post when I wrote it last night, I had a feeling that I'd forgotten something, but I couldn't think of what it was.

Now I remember: Kudos to Bill for helping me come up with the solution. It was late and I was tired when I was working out the best way to test a flags value, and he came in and pointed me in the right direction. Thanks dude :)
# May 25, 2005 7:14 PM

Bill McCarthy said:

Ah shucks, I'm just "overflowing" now ;)
# May 25, 2005 7:26 PM

Geoff Appleby said:

Dweeb :P
# May 25, 2005 7:29 PM

Bill McCarthy said:

LOL... but seriously though, can't it also cause overflow excpetions ?
# May 25, 2005 7:46 PM

Geoff Appleby said:

I can see one in there: summing up the values could possibly end up being larger than max_int (C speak there :)

In fact, each individual specific enum value could be larger than max_int. Have I missed any Bill?

Also, another thing to note is that I've now discovered that System.Enum.IsDefined does actually use reflection :/
# May 25, 2005 8:06 PM

Bill McCarthy said:

Oh dear, jsut looked closely at your code... You should NEVER "sum" the values, it shoudl be "or"'d together as a flag enum value is not guaranteed to be mutually exclusive (and often is not)
# May 25, 2005 8:20 PM

Geoff Appleby said:

Well crap. You're bloody right!

I KNOW this, but I still always go and add anyway.

Thanks dude, maybe I'll remember this time :)
# June 1, 2005 9:43 PM

Public Class GeoffAppleby said:

A few weeks ago I wrote a post titled 'A Little Enum Tip'.
In it I discussed mapping some random Integer...
# June 9, 2005 10:24 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

To submit your comment, click on these pictures:
  • Geoff's dad's tongue
  • Geoff's tongue
  • Geoff the big mouth
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