VB Curioddities #1: Enum, Enum, my kingdom for an Enum.Parse

VB Curioddities #1: Enum, Enum, my kingdom for an Enum.Parse

Rate This
  • Comments 20

Hey folks, my name's Kit George and I've joined the VB team from the CLR. VB is after all, the best language, so of course, it makes sense to work directly on it!

Like all languages, VB has it's little 'oddities', so i thought i would start a series to present a few of these. These are little (and perhaps large) curious VB things that you may or may not have noticed. Either way, you can bring them up at the water cooler to impress everyone with your VB knowledge.

I thought I would start with an interesting oddity surrounding Enum.Parse. Enum is of course, a keyword in Visual Basic, since it was already a keyword in days prior to .NET. This creates an interesting conflict between the Enum class (Enum is itself, a class in the System namespace of .NET), and the keyword. Why it becomes interesting is that the Enum class has some Shared members on it, most interestingly, Parse. You would normally invoke such a method with code like this:

  Public Class Test
  Public shared Sub Main()

    Dim ct as CarType = Enum.Parse(GetType(CarType), "Sedan")

  End Sub
 End Class

 Enum CarType
  Sedan = 1
  Sports = 2
  SUV = 3
 End Enum

However, because Enum is a keyword, you'll get an exception on the above code because the 'Enum.Parse' line is treated as if you're trying to declare an Enum by the compiler. You could workaround this easily by simply doing the followin. The square brackets around the name Enum tell the compiler to NOT treat it like a keyword, therefore, it treats it like a class call:

    Dim ct as CarType = [Enum].Parse(GetType(CarType), "Sedan")

But in addition to the above syntax, and in order to make it even simpler, VB allows you to use the actual name of the Enum (CarType in this case) to invoke the Parse method. To all intents and purpose, this LOOKS like a Shared method call, so it should be supported:

    Dim ct as CarType = CarType.Parse(GetType(CarType), "Sedan")

Now here's where it gets interesting. Under the hood, the above line is actually turned into a call through to [Enum].Parse. That is, the CarType.Parse call has nothing to do with the CarType Enum itself, instead, it simply becomes a standard [Enum].Parse call. Therefore, what if I did something like this:

 Enum BoatType
  Trawler = 1
  SpeedBoat = 2
  CruiseShip = 3
 End Enum

    Dim ct as CarType = BoatType.Parse(GetType(CarType), "Sedan")

Surely this wouldn't compile, right? After all, it looks like we're parsing on the BoatType class, into a CarType type: that surely doesn't work.

But remember that CarType.Parse simply got turned into [Enum].Parse? Well, ANY other Enum call is the same. So BoatType.Parse, really just becomes [Enum].Parse. And this works just fine! So the above code compiles and runs just fine. Of course, I would not suggest under any circumstances, that you write your code this way, it is odd to read.

Note that of course, this will NOT work:

    Dim bt as BoatType = BoatType.Parse(GetType(CarType), "Trawler")

The reason is that the KEY things for the transformation to succeed are that a) the type on the left of the assignment, and the type in the first parameter to the Parse method, match (they don't in the above case, which is what causes it to fail), and b) that the string can be converted to the specified type (That is, it is a member of the specified Enum. In the above case it can, but of course, we're gonna fail here because of item a) anyway).

At the end of the day, my own preference IS to use the name of the specific Enum, but simply use the same name as the Enum I'm parsing into. But I do like messing with people from time to time with this one ;-).

Leave a Comment
  • Please add 5 and 3 and type the answer here:
  • Post
  • They should probably add a generic version to eliminate the redundant paramter and cast.

    MyEnum en = Enum.Parse<MyEnum>("Cool");
    Dim en As MyEnum = [Enum].Parse(Of MyEnum)("Cool");

    (Sorry if that VB syntax is wrong for generic methods. I don't use VB much.)
  • Kit、

    I didn't think you'd switch to the dark side (VB).
    I hope the CLR team is still in good hands.
  • Hey Kit,

    Welcome to the land of happy code.

    Shame on you for not mentioning that your above samples only work with Option Strict Off :)

    There's also another alternative (which is my preffered way of doing it). The compiler only get confused when using Enum.Parse because there's no context.

    Rather than confuse the issue with a specific type (although messing with people is always fun), I just give it the right context:

    Dim ct as CarType = System.Enum.Parse(GetType(CarType), "Sedan")

    Again, it all compiles to the same thing, but to me it's clearer :)

    I really like Josh's idea of the generic parse method too. Even with Option Strict On, you wouldn't need a final cast for the assignment. In fact, here's a working (minimal) implementation.

       Public Function Parse(Of T)(ByVal value As String) As T
           Return DirectCast(System.Enum.Parse(GetType(T), value), T)
       End Function
  • An even better idea. When you declare a delegate type the compiler spits out a BeginInvoke that is relevant. Why not just make the compiler spit out type specific Parse methods?

    While we're on that topic if you could manage a type-safe short-circuiting Iif expression it would be ever so appreciated.

    As for backwards compatabelity Iif(True, (A), (B)) would still circumvent the short circuiting no?

    Kudos to whoever put String.Contains and String.IsNullOrEmpty into the framework this time, sometimes it's the little things...
  • A generic Enum.Parse is a great idea.
    There is a suggestion for it on Connect:

    A simple wrapper method such as what Geoff Appleby wrote, while "hiding" the cast, does nothing about boxing. That issue needs to be solved at a lower lever--in the framework.
  • Hey me,
    Yes, you're right, I forgot to mention that bit. While we can implement it ourselves, it really does need to be inside the Enum class itself.
  • Heh - also, I'm pretty sure we don't need to convince anyone here of the fact. If you have a look at one of the duplicates listed against that link to Connect, it was Kit who closed one of them with a 'we'll try for vNext' :)
  • Kit, good to see you've come over to the dark side. I remember meeting you when the CLR team was in Atlanta last year. Good luck in the new role.
  • > Dim ct as CarType = System.Enum.Parse(GetType(CarType), "Sedan")

    That silly. Why not just make it a standard method on the real type itself?

    Dim ct as CarType = CarType.Parse("Sedan")

    Isn't that a whole lot cleaner?

    And while we are at it, how come I cannot declare methods on my Enums? It really sucks having to place all the methods like "ConvertThisEnumInToHumanReadableText" in a seperate module.
  • Jonathan, Extension Methods in VB9 may be an answer for your desire to add methods to enums. I have a write-up at http://devauthority.com/blogs/jwooley/archive/2006/05/21/1095.aspx.
  • Thanks all for the welcome folks! First, the CLR of course, is always in good hands (although feel free to use me as a reference point into them if you have no-one else, kitg@microsoft.com). Second, the dark side? I will be making sure "you shall pay for your lack of vision". I agree with Anthony, this is the land of happy code, I've always had a smile on my face when i get to code in VB.

    Jonathan said 'Why not just make it a standard method on the real type itself?
          Dim ct as CarType = CarType.Parse("Sedan")
    We could do additional work here Jonathan, but it would be considerable work, with little benefit. More importantly, it would be a breaking change, therefore, we're unlikely to ever make that kind of change. It's one of those 'the ship has sailed' situations.

    Geoff said 'Shame on you for not mentioning that your above samples only work with Option Strict Off'
    Fair, fair. The only change for those who are curious is that you need to use CType to convert the object returned by Enum.Parse, to the specific Type you're converting to:
       Dim ct as CarType = CType(CarType.Parse(GetType(CarType), "Sedan"), CarType)
    The fact that an object type is returned from Parse leads nicely into the next point above:

    Comment made by various folks: why not add a generic Enum.Parse method?
    So yes, we have had this request before, and thanks for it, it is a great idea (hint: it's always a good idea to reping teams after they've said 'we'll consider this in a future version' to see how that consideration is coming along). You won't see this kind of addition in Orcas unless we did add it as an extension method, I'll be reforwarding the request onto the BCL team. It would certainly make the operation a lot more elegant, there's no doubt about that. Anthony's proposed implementation would do the trick.

    Geoff's proposed solution is also a nice mechanism to resolve this issue, simply fully qualify Enum:
      Dim ct as CarType = System.Enum.Parse(GetType(CarType), "Sedan")
    Personally, I NEVER like fully qualifying stuff, my knee just kinda starts jerking.

    Anthony said 'Why not just make the compiler spit out type specific Parse methods?'
    This is an interesting idea, but I'm not sure we would ever do it. It would take a LOT of compiler work to make this happen (detect when to spit the type specific Parse, vs. not), and would require compiler work. Plus, what does it mean for the contract of a method? Generics seems to achieve the same objective.

    Finally, Josh, on your vb syntax: looks good, apart from the trailing darkside at the end of your line. Uh, I mean, semi-colon ;-).
  • of course if you have an enum such as :

    Public Enum Operations
    End Enum

    Then code such as

    Dim op As Operations = CType(Operations.Parse("parse"), Operations)

    won't even compile as the field hides the shared method of System.Enum.

    So perhaps what we need is
    - as has been mentioned a generic method on the base System.Enum;
    - the compiler to suggest to "fix" code that tries to use Parse from a specific enum;
    - relaxed rules on protected keywords (surely the []'s are note needed for Enum.Parse)

  • Extensions are interesting, but they don't really address all of my issues.

    In OOP, methods that affect data are supposed to be defined along with the structure that holds the data.

    With extension methods, you get the illusion of OOP when using the Enum. However, you are still storing the function in a different place than the structure with its associated maintenance costs. In fact, the maintenance cost is a bit higher because it isn't clear where the extension lives.

    Extension methods have the additional cost of being a language feature. This means that they won't be readily available to other languages. (Are they even CLS compliant?)

    From a documentation stand-point, where do Extensions go? In their own section? If so, they will be hard to find there. Or with the class they are extending? But that means we need special notes indicating that some methods are not in the same assembly as the rest of the class.

    My proposal to allow methods on enumerations shouldn't be hard to implement. Enumerations are just a special case of a Value type, and all other Value types already allow methods.

    The only issue I see with the syntax is it can get confusing if enum values are mixed in with the methods. To whit I suggest that all enum values must be defined before any methods.
  • > Jonathan said 'Why not just make it a standard method on the real type itself?
         Dim ct as CarType = CarType.Parse("Sedan")
    We could do additional work here Jonathan, but it would be considerable work, with little benefit. More importantly, it would be a breaking change, therefore, we're unlikely to ever make that kind of change. It's one of those 'the ship has sailed' situations.

    Ok, maybe automatically generating the method would be bad. But is there any reason why we cannot have method on Enumerations?
  • Jonathan, you can't expect every language to suddenly add parsing methods to each enum they declare.
    Enums are a very special case of value type as they themselves derive from a value type, something that is techincially impossible. It's lots of smoke and mirrors that is actually going on, and for the most part, enums really are just a design time metadata thing, only being present in type (and parameter) declarations, but their values are compiled as constants usign the underlying base type (e.g. Int32).  So in the stack frame you don't even have an enum there, just an Int32, so you aren't really going to be able ot call a virtual method on it.  Sure some compilers could try to fudge this, but you'd end up with a lot of inconsistencies, and more work, and performance hits that what it would be worth.
    As to Extension methods, they are really no different from what we use today with the Convert class, except they appear via the . syntax and so thee is a great intellisense story.  They are of course CLS compliant as they are jsut shared methods. Any language can use them without the need for language support. Language support is only needed for the "." wizadry, which compiles exactly the same as if it is using the Shared method approach (e.g COnvert class)
    As to documentation, hopefully the docs will include an "Extensions" section. that should be no problem for the classes MS produces, for your own, or third party extensions, I can't see how you could safely include that.  But it is important to understand that you won't be using your own or third party extnesions wihtout Import'ing then, so the help for them would be via the import class name.  I'd imagine f1 help couldresolve this to the actual shared method jsut like the compiler does.

Page 1 of 2 (20 items) 12