About 8 months ago, I promised to explain why the C# ‘as’ operator was worth adding to VB and then never got back to it. While trying to figure out what comments I missed, I came across the promise again and figured I’d take the opportunity to rectify the situation.
In Whidbey, we’re going to introduce YACO (Yet Another Conversion Operator) called TryCast that’s equivalent to the C# ‘as’ operator. Why? It turns out that it’s useful in some specific, but not uncommon, situations. Let’s say that you want to write a method that accepts an object and, if it implements a particular interface, do something with it. For example:
Sub Print(ByVal o As Object)
Dim PrintableObject As IPrintable
If TypeOf o Is IPrintable Then
PrintableObject = DirectCast(o, IPrintable)
Ok, great, this works fine. The problem is, though, that we’re doing redundant work here. You see, when the CLR executes the If statement, it does a type check for the TypeOf expression. If o does implement IPrintable, it then goes ahead and casts o to IPrintable, which does another type check to ensure that the value really does implement IPrintable. So you’re doing two type checks, and type checks can be expensive. (When I say “expensive” here, I don’t mean expensive like “$760,000 for a Ferrari” expensive, I mean more like “$3.50 for a latte twice a day adds up over 365 days” expensive.)
What TryCast does is it allows you to combine the two type checks into one. TryCast, as its name suggests, will try the cast and, if it succeeds, return the value cast to that type. Otherwise, it returns the value Nothing. So you can rewrite the code above as:
Sub Print(ByVal o As Object)
Dim PrintableObject As IPrintable = TryCast(o, IPrintable)
If PrintableObject IsNot Nothing Then
Voila, two type checks becomes one! Even if we hadn’t gotten any feature requests for this, we were planning to do it anyway because we found that several of our language helpers that deal with object values could be sped up ~5% just by eliminating the redundant type checks. So it’s not nothing. (So to speak.)
One question that might come to mind is: why not just optimize the first code into the second code under the covers instead of introducing a new operator? We did consider this at the compiler level, and the CLR could also choose to do it at the JIT level. However, while we could optimize this particular case, it’s easy to construct cases that would be difficult for the compiler or JIT to optimize. Which would mean that you could make a minor tweak to the structure of your code and suddenly lose the optimization without knowing it. In this case we figured explicitness was better…
Update 4/3/04: Made some corrections based on comments below.
In the first example, why are you using CType instead of DirectCast? My understanding is that CType should be used when the type might need to be converted, but DirectCast is more efficient for simple casting.
Since you *know* (after the first test) that o has an IPrintable interface, wouldn’t a DirectCast be more efficient?
Cool! I’ll definately be on the list of users for this one. Just out of curiosity, although TryCast is fine, what was the decision process for coming to that keyword? Why not TryCType or TryDirectCast? Of course, TryCast does sound pretty good 😉 Maybe you can put an alias in the language so that Cast maps to CType? That way you would have Cast and TryCast. Just a thought.
Robert, you’re entirely correct: DirectCast would be more efficient in the first example!
Cory, I’ve been meaning to talk about "how we come up with keywords" for a while now, maybe I’ll do it soon!
>> Voila, one type check becomes two!
Don’t you mean "two type checks become one"?
Just a suggestion:
Sub Print(ByVal o As Object)
If TryCast(o, PrintableObject As IPrintable) Then
Wouldn’t this get rid of an If (on IL level) as well?
Why TryCast? The as in C# seems to be more VBish that TryCast…:-(
DevDays is coming up. Will we have newest Whidbey bits?
Scott: Thanks for the correction!
Rolf: Although the syntax is more compact, it won’t eliminate any additional IL.
Anand: ‘As’ was already taken. Otherwise, you’d end up with "Dim x As y = z As y" which confuses operators and declarations.
vbNullString: I’m not sure _what_ the plan of record is for DevDays, sorry!
Seems to be something wrong with your RSS feed… there’s an incorrectly formatted BR tag (space between the / and >). Just to let you know. 🙂
And your cookie is not working. It doesn’t remember me when I come back to comment form.
I see that Cory has posted a topic that I have been thinking about.
Why did VB.NET choose the Keyword CType instead of Cast? I think that Cast is more readable – and implies what is happening.
Then we could have Cast, DirectCast and TryCast. The naming conventions are stronger this way.
Well, originally there was no DirectCast or TryCast, just CInt, CBool, CStr, etc. Given that, CType is consistent with the other "C" conversion operators. There are really two "camps" of conversion operators: the basic ones and the advanced ones. We consider CType to be one of the basic conversion operators, so it makes sense that it’s named like the others. DirectCast and TryCast are the more advanced operators.
Paul, I completely agree with you in the well chosen CType operator. To me, it’s the generic version of all the Cxxx operators. For example, I could use CType(o, Integer); but CInt seems much cleaner and clearer. DirectCast seems almost like an afterthought and if you are aware that it could possibly improve the performance involved in casting of reference types, you’d likely take advantage of it. However, during normal coding tasks, DirectCast is kind of unnecessary and you could do well in your programming career if you never used it. This is not to say that DirectCast is useless, by all means, I definately use it all the time. Just saying that most programming tasks, the performance benefits aren’t really visible.
However, now that we are going to have another operator that sounds similar (TryCast), it really seems like Cast should be added as an alias operator (exactly the same behavior as CType) so that way, from someone learning the language, it’s makes more sense to logically group these three keywords together. I think it also doesn’t ‘break’ the Cxxx operators either, since it’s sharing the same letters between the two.
Historically, I think I’ve always saw the C in the Cxxx operators as Convert or ConvertTo… but it’s not a stretch to think of it as Cast. I’m I off base on this? I’m sure you guys have already considered this, just curious why you would not decided to alias the CType operator. The only downside that I can think of this that it could potentially cause developers to complain about having two keywords that do the same exact thing and not knowing when to use one vs the other. Of course, then I could argue about the need to have IsNot in the same light 😉 With IsNot, we will have two ways to check for the non-existance of value types (three if you consider checking to see if something is not equal to nothing).
"As" is one of my favorite C# feature, having a counterpart in VB.NET is a great new.
Paul, is there any reason preferring DirectCast vs TryCast on Whidbey?
Corrado: I could be wrong, but DirectCast and TryCast aren’t at all interchangable. Each has an appropriate turn to be used.
TryCast will TRY to CAST and if it fails, from Paul’s example, will return null. If DirectCast fails, it’ll throw an exception.
You use TryCast when you aren’t sure what interface an objcet implements. You use DirectCast when you know exactly what an object implements. I see it as the same difference between Int32.Parse and Int32.TryParse()
Of course, I could be wrong.
I suspect the main difference between using DirectCast vs TryCast will be that of performance. If you know that you will not be getting an exception due to the reference being nothing, then DirectCast is you best bet. However, if you are unsure and want to be safe, then take a minor performance hit and use TryCast.
This is a nice feature indeed, though "AsType" name used in Roeder’s Reflector seems nicer to me.
So how do you choose the operator names for Vb.Net ?
As example, why >> and << where used instead of Pascal-like "Shl" and "Shr",
which IMHO are more conformant with "Mod", "And", "Or" etc ?
AndAlso why do we have CType instead of simple Cast ?
And the second question – why new keywords appear VB .Net so late compared with C# ?
I do not really understand why there was no "Using" in Vb .Net before Whidbey.
Anyway, it is very nice to see that VB is improving.
Paul will surely correct me if I’m wrong (as I’m just a web guy in SoCal, not a Microsoftie), but in that particular case, the ‘using’ syntax for IDisposable was a very late addition to C#, added in response to C++. VB.NET ‘Imports’ corresponds to C# ‘using’ most of the time, but using Imports to auto-IDisposable.Dispose would be a bit odd.
I’m not sure what’s up with the bit shift operators, myself. They seem really un-VB to me.
Err… correction on that…
… the ‘using’ syntax for IDisposable was a very late addition to C#, added in response to C++.
should have been
… the ‘using’ syntax for IDisposable was a very late addition to C#, added in response to C++ people complaining about .NET’s lack of support for anything resembling deterministic finalization.
Why shift operators? Because they are needed. The Win32 methods aren’t going away anytime soon and it’s always been a pain working with some of those API’s since they rely heavily on bit operatations. The only flaw with bit shift operators today is the fact that they don’t work with unsigned types (which will be corrected in Whidbey). Besides PowerBASIC has had bit shift operators for over a decade… it’s about time that they made it VB.
Also, I believe Paul explained why the Using statement was not part of the current versions of VB.NET in a post a while back. I think it had something to do about trying to get VB.NET to have deterministic finalizations and have it work similar to VB6 (I could be a little off on that) and when that fell through, it was a little late in the game to get Using in there.
It’s not the existence of shift operators that’s problematic (I won’t use them much, but there are lot of features in VB and the .NET framework that I don’t use much). It’s that they’ve got C-style syntax; the chevron-style operators ( << and >> ) just aren’t all that meaningful, and look hopelessly out of place in VB code.
Now that DirectCast is used in the sample code is it any less efficient than the ‘new’ code with TryCast. DirectCast already eliminates a type check, or do I misunderstand its run-time performance savings over CType?
DirectCast skips a helper that CType calls when the source type is Object. So you skip different work than if you used TryCast.
Cory:>This is not to say that DirectCast is useless, by all means, I definately use it all the time. Just saying that most programming tasks, the performance benefits aren’t really visible.
True in general, but put that to use in a tight loop and things begin to look a bit different 🙂
Cory:>Historically, I think I’ve always saw the C in the Cxxx operators as Convert or ConvertTo… but it’s not a stretch to think of it as Cast. I’m I off base on this?
Casting and converting are two similar, but subtly different ideas. In the C/C++ sense of the word, casting is about reintepreting an arbitrarily typed value as another arbitrarily typed value, regardless of whether or not it makes sense to do so. Converting on the other hand, implies compatability between the types and usually involves a translation between the bit patterns of the values of different types. Both of these operations are represented in VB as CType and DirectCast respectively (although with added type checks.)
Nice Paul, but I have a question/suggestion:
Why not a "CastAs" operator that can be used inbetween an operand and type, i.e.
Dim x As String
x= (100 CastAs String)
Sure does read a lot more like VB kinda syntax, and is tons more readable than:
Dim x As String
CType() has never felt right to me because it looks like a function, yet its second parameter is not an expression. So CType() is a goofy special case that has to be implemented as a special case in the compiler. I always hate it when language designers do that! 🙂
Mike: Well, CType was going to have to be implemented in the language anyway, just like CInt, CBool, etc, but I get your point. We’ll take the suggestion and look at it, although I don’t know now if we’d want to introduce yet another way to cast values. We’ll see, though. Thanks!
That CType() special case would be nice if *WE* could write functions like that. Hey Paul, did you and your team ever consider allowing a type name to act as a literal value for System.Type?
This would allow things like:
Dim t As Type = Integer
as opposed to the equivalent but more round-about way:
Dim t As Type = GetType(Integer)
Allowing a typename in expressions to refer to the Type object for it would conflict for member access. Would "Integer.GetType()" return the type object for Integer or Type?
Paul:>Allowing a typename in expressions to refer to the Type object for it would conflict for member access. Would "Integer.GetType()" return the type object for Integer or Type?
I imagine GetType() exists specifically because we cant use the typename as a literal value for the System.Type class.
Furthermore member access doesnt really conflict because when you access a member, you are not specifying a type name i.e GetType() is *not* the name of a type. Note the suggestion was that when you assign a *type name* to a variable of type System.Type, it would be equivalent to assining the value of GetType(TheTypeName). To get the type object for Sytem.Type you would do:
Dim t As Type = System.Type
which admittedly looks a wee bit weird but this is the only case.
Yeah, I suppose you could say that an expression that’s a type could be converted into System.Type… Something to think about, I suppose.
Pingback: Robert McLaws: FunWithCoding.NET
Pingback: Robert McLaws: FunWithCoding.NET
Isn’t directcast someone what similar to trycast()? Please correct me if im wrong. DirectCast like ‘As’ Operator in c# will fail if the type being held within the object is not of the type you are converting the value to. The main difference being ‘As’ will return null/nothing if it fails but behind the scenes they are pretty much doing the same thing and both will not use the internal conversion operators like a cast would. So say if you wanted to implement a TryCast today you could pretty much do this
catch ex as InvalidCastException
Like i said before please correct me if i’m wrong.
Pingback: Samurai Programmer.com