At a client's request, I've been porting my Tablet PC labs to VB.NET. In the process, I've learned some lessons that might be helpful to others who have to make this sort of a port. This is strictly programmer geek stuff, so I'm hiding it. The rest of you may prefer to
Note: In no way is this intended as a slam against VB.NET. Anything I can do with C#, I can do with VB.NET, and vice versa. It's simply a pair of lessons to keep in mind if you work in both languages.
But before the lessons, let me briefly describe the porting process, since either of these lessons might not have been learned if I had written the VB.NET code from scratch. I started with 200 pages of C# labs (yes, folks, my Tablet PC class is that intensive, and more). Then I built a VB.NET project, recreated the UI elements by hand, pasted in the C# code, and edited the code until it was syntactically correct for VB.NET. And except for these two lessons, it seems to have worked well.
Lesson 1: Divide <> Divide
I had a strange bug in the VB.NET version of the labs. In C#, I could scroll the document to the end of the page. In VB.NET, I would get an exception after the scroll passed the half-way point. Digging into the debugger, I found that my scroll bar ranged from 0 to 99; but I was trying to set the scroll bar value to 101. Further investigation led me to discover differences in the behavior of division in the two languages.
Let's look at some C# code first:
static void Main(string[] args)
{
object result = 51 / 100;
Console.WriteLine(result.GetType().Name +
" - " + result.ToString());
}
This code divides two integers and stores the result in an
object, the .NET base type that all other types derive from. That means that any value can be stores as an object. The code then displays the real type of the object, as well as a string representation of the value of the object. And here's the result:
Int32 - 0
Now here's what
seems like the same VB.NET code. This is exactly what I get by following my copy-and-make-it-compile porting strategy:
Sub Main()
Dim result As Object = 51 / 100
Console.WriteLine(result.GetType().Name + _
" - " + result.ToString())
End Sub
And here's the result:
Double - 0.51
This is probably not news to any VB or VB.NET or even just Basic programmers out there; but it's important for porting programmers to keep in mind:
In Basic, any numeric divisor or dividend is converted to a Double (i.e., a double-precision floating point value) before the division, and the resulting quotient is a Double.
Anyone who knows VB.NET will see the obvious error in my port: I should have used the special integer division operator, \:
Sub Main()
Dim result As Object = 51 \ 100
Console.WriteLine(result.GetType().Name + _
" - " + result.ToString())
End Sub
And here's the result:
Int32 - 0
The results are exactly the same as we saw with the original C# code.
The problem with this lesson is that it completely undermines my copy-and-make-it-compile porting strategy: the copied division symbol
compiles, but it doesn't yield the same results.
And it gets slightly more complicated yet. Let's modify that C# code:
static void Main(string[] args)
{
object result = (int)(51.0f / 100.0f);
Console.WriteLine(result.GetType().Name +
" - " + result.ToString());
}
Here, I
know I'm dividing floating point values; but then I cast back to an int. And here's the result:
Int32 - 0
It's exactly the same as the original, because the final cast truncates the fractional portion.
Now here's the "same" code, VB.NET style:
Sub Main()
Dim result As Object = CInt(51.0f / 100.0f)
Console.WriteLine(result.GetType().Name + _
" - " + result.ToString())
End Sub
And here's the result:
Int32 - 1
The result is an integer, just like in C#; but instead of the remainder being truncated, it's rounded, either up or down. (And then, just to offend mathematical norms, a precise value of 0.5 will round
down.)
So I could see that the division results needed to be converted; but I didn't see that as they were converted, they were rounding.
Keep in mind as you port: Divide != Divide.
Lesson 2: Name <> Name
Inside a C# Form (i.e., inherited from System.Windows.Forms.Form), I wrote code that looked like this:
private void OpenDrawing()
{
if (dlgOpen.ShowDialog(this) == DialogResult.OK)
{
OpenDrawing(dlgOpen.FileName);
}
}
Ported to a VB.NET Form, the code looked like this:
Private Sub OpenDrawing()
If (dlgOpen.ShowDialog(Me) = DialogResult.OK) Then
OpenDrawing(dlgOpen.FileName)
End If
End Sub
Both versions compiled. Both versions ran correctly. But when I compiled the VB.NET version, I got this warning on the highlighted code:
Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated.
After a little thought, I remembered that System.Windows.Forms.Form has a property of type DialogResult that is also
called DialogResult
and that the return type from ShowDialog is
also of type DialogResult.
Now you might question the wisdom of using the same name for a property and its type. Heck, I've done it a lot myself, but now
I'm questioning the wisdom. Because it appears that VB.NET and C# have different rules for resolving that name: C# looks to the name first, while VB.NET looks to the property first. Now as it happens, VB.NET sees the property used as a qualifier to access a value within the type, sees the qualifier as superfluous, issues a warning, and just uses the type. So the end result is the same; but the VB.NET programmer gets a confusing or possible scary warning message.
Now there's a very easy way to avoid that message: simply be explicit. Here's a modified version:
Private Sub OpenDrawing()
If (dlgOpen.ShowDialog(Me) = System.Windows.Forms.Form.DialogResult.OK) Then
OpenDrawing(dlgOpen.FileName)
End If
End Sub
When you explicitly qualify the type, VB.NET won't be confused about what you mean, and the warning goes away.