The .NET Common Language Runtime (CLR) executes the Microsoft Intermediate Language (MSIL). Code written in one of .NET’s various supported languages is translated into MSIL and metadata. Your compiled classes are referenced through metadata and used in this MSIL. This makes it possible to use code written in different languages. You can aggregate or associate an object of a class written in C#, for instance, within a VB.NET program. You can even inherit from a class written in a different language.
The exciting implication of this is that .NET does not restrict you to any language. If you are a C++ or Java programmer, you can use C# (or managed C++). If you are a VB programmer, you can use VB.NET. Or you can use one of the other 25+ languages supported in .NET. (Refer to “Languages available in .NET” in the section "on the web" in Appendix)
However, there are a few glitches to keep in mind. Not all the languages support every MSIL feature. Furthermore, each language has its own set of keywords; although there are some commonalities in keywords between languages, there are many differences to watch out for. What kind of mess can you get into with these differences? What are the things you need to remember to make your code interoperable between languages? These issues are the focus of this chapter.
When I first started learning C#, I heard that it has some C++ features that are not in Java. My first thought was templates. I was quite disappointed to find instead that one of the supported features is operator overloading. It’s quite complicated in C++ [Stroustrup00, Cline99]; it gives you many ways to shoot yourself in the foot. Having offered courses on C++ for over 12 years, I know the pain people go through just learning operator overloading. Getting it right is another story [Meyers96, Meyers98]. Overloading is supposed to make your code intuitive and easier to understand, but it often has the opposite effect.
Unfortunately, in my opinion, C# took what was complicated in C++ and made it worse. For instance, you can overload the true
operator. This requires that you overload the false
operator as well. Arbitrarily overloading operators increases the complexity of your code and reduces its readability; its worth is questionable.
Furthermore, not all .NET languages support operator overloading. If you write a C# class with overloaded operators, those operators are not accessible from VB.NET. If you have merely overloaded the operators and have not provided equivalent regular methods, your code is not considered to be Common Language Specification (CLS) compliant. CLS compliance requires you to provide a regular method for each overloaded operator.
Consider the example of delegates in .NET. Typically a C# programmer would use the +=
operator to add or register a delegate. In VB.NET, you can realize the same goal using either the Combine()
method or the special AddHandler
statement. Similarly, the Remove()
method is equivalent to the C# -=
operator on delegates. Providing regular methods for each overloaded operator allows users of your class to take advantage of the operators in languages that support overloading, while making it fully usable in languages that don’t.
If you want your code to be CLS-compliant, then for each operator you must provide a regular method as well. Operator overloading does not reduce the code size. In fact, you will end up writing more methods to accommodate languages that don’t support it.
Always provide a regular method for each overloaded operator. Mark your assembly with the CLSCompliant
attribute if you intend to provide language interoperability. Then use the FxCop
tool to confirm that you are CLS-compliant.
Gotcha #31, "Optional parameters break interoperability.”
An optional parameter in VB.NET is a parameter that is given a value at the point of declaration. When you call a method that has optional parameters , you can choose not to provide a value for them. The compiler will substitute the default values. However, not all .NET languages support optional parameters. VB.NET allows you to declare trailing arguments as optional. C# does not. What happens to interoperability if you use optional parameters in VB.NET? Consider Example 4-1.
Example 4-1. Using optional parameters
'SomeClass1.vb part of AVBDNLibrary.dll Public Class SomeClass1 Public Overridable Sub SomeMethod(ByVal val1 As Integer, _ Optional ByVal val2 As Double = 0.0) End Sub End Class //Test.cs part of CSUser.exe using System; namespace CSUser { class Test { [STAThread] static void Main(string[] args) { AVBDNLibrary.SomeClass1 obj = new AVBDNLibrary.SomeClass1(); obj.SomeMethod(1, 2); } } }
The VB.NET class SomeClass1
has a method SomeMethod()
with two parameters, the second one marked Optional
. You also have a C# class Test
that, in its Main()
method, creates an instance of the VB.NET class SomeClass1
and then calls its SomeMethod()
. If you look at this example, there seems to be no problem in calling the method SomeMethod()
of SomeClass1
. In fact, IntelliSense displays the method signature in C# without showing any indication of default or optional arguments, as seen in Figure 4-1.
Well, there is no problem if this is the level of interoperability you are looking for. However, things get tricky if you go further. To illustrate what can happen, let’s derive a C# class from SomeClass1
. This is shown in Example 4-2.
Example 4-2. Deriving a C# class from the VB.NET class in Example 4-1
Note that the overridden method SomeMethod()
takes two parameters, int
val1
and double
val2
. Since C# does not support optional parameters, both the parameters are specified.
So far so good, right? Now say you create a VB.NET class that derives from SomeClass2
, as in Example 4-3.
Example 4-3. Deriving a VB.NET class from the C# class in Example 4-2
✗C# and VB.NET (Optional)
'SomeClass3.vb part of AVBDNLibrary2.dll Public Class SomeClass3 Inherits CSLib.SomeClass2 Public Overrides Sub SomeMethod(ByVal val1 As Integer, _ ByVal val2 As Double) End Sub End Class
Now that SomeClass3
inherits from SomeClass2
, the overridden method does not have any optional parameters. When you compile this code, you get the error shown in Figure 4-2.
The error message tells you that SomeClass3
cannot override SomeMethod()
because the base version and the overridden version differ by the optional parameter. What if you try to fix this error by placing the Optional
keyword in SomeClass3.SomeMethod()
, as in Example 4-4?
Example 4-4. Placing the Optional keyword in the overriding method
✗C# and VB.NET (Optional)
'SomeClass3.vb part of AVBDNLibrary2.dll Public Class SomeClass3 Inherits CSLib.SomeClass2 Public Overrides Sub SomeMethod(ByVal val1 As Integer, _ Optional ByVal val2 As Double = 0.0) End Sub End Class
No go. You get the same error (see Figure 4-3).
The compiler is still not happy. Why? It’s because there’s a conflict here. The first-level base-class method says it needs an optional parameter. The second-level immediate base class, however, says it does not have any optional parameters (being a C# class). If you look at the MSIL generated for each of these classes using ildasm.exe, you will notice that SomeClass1.SomeMethod()
in the VB.NET base class has [opt]
specified. However, the method in the C# class does not.
Not all .NET languages support optional parameters. This may lead to interoperability problems. Consider using method overloading instead of optional parameters.
Gotcha #16, "Default of Option Strict (off) isn’t good,” Gotcha #30, "Common Language Specification Compliance isn’t the default,” Gotcha #32, "Mixing case between class members breaks interoperability,” Gotcha #33, "Name collision with keywords breaks interoperability,” and Gotcha #34, "Defining an array isn’t consistent.”
Do not create properties or methods that differ only by case. C# (like C++ and Java) is case-sensitive. VB.NET (like VB6) is not. So how would VB.NET handle a C# class that has two different properties with the same name (i.e., their spelling differed only by case)? This is illustrated in Example 4-5.
Here you have a C# class SomeClass
with two properties, Value1
and value1
. The C# compiler has no objection to this. Now let’s write a VB.NET class that uses the above class, as in Example 4-6.
Example 4-6. Using C# class with mixed-case members
✗C# and VB.NET (DifferInCase)
'TestModule.vb part of VBUser.exe Module TestModule Sub Main() Dim obj As New CSLib.SomeClass Dim val As Integer val = obj.Value1 End Sub End Module
When you compile, you get the error shown in Figure 4-4.
In .NET Version 1.0, this compilation error did not appear. Instead the first property with that name was used. In this case, Value1
would have been used instead of value1
.
How does this differ in .NET 2.0 Beta 1? There is no compilation error; the behavior is the same as in .NET version 1.0.
These two properties can be accessed in C# without any problem. The C# compiler resolves the names properly. The VB.NET compiler, however, gets confused about this “overloading.” Incidentally, you can use reflection in VB.NET to access these two properties, because the CLR uses case-sensitive matching to determine the members.
Do not write properties, methods, or fields whose names differ only by case. This breaks interoperability.
Gotcha #31, "Optional parameters break interoperability,” Gotcha #33, "Name collision with keywords breaks interoperability,” and Gotcha #34, "Defining an array isn’t consistent.”
When choosing names for methods or properties, avoid names that are keywords in other .NET languages. Most of the time this won’t be an issue, for you would be unlikely to choose a name like new
, structure
, or class
. However, I’ve been surprised at times by the names I chose. Consider Example 4-7.
A C# class named StopWatch
models a stopwatch that will allow you to measure the time spent in certain activities or events. It has a method named Start()
that gets the stopwatch going. The Stop()
method halts it. Reset()
is used to simply clear the stopwatch. The ElapsedTime
property gives you the time that has elapsed. Let’s just leave the methods and property with a dummy implementation for this example. The code compiles without any error. So, what’s the problem?
Let’s create a VB.NET class that derives from this, as in Example 4-8.
Example 4-8. A VB.NET class derived from the class in Example 4-7
✓C# and VB.NET (WhatsInAName)
'SpecialStopWatch.vb part of VBLib.dll Public Class SpecialStopWatch Inherits CSLib.StopWatch Public Overrides Sub Reset() End Sub Public Overrides Sub Start() End Sub Public Overrides Sub Stop() End Sub End Class
When you compile the code you get an error, as in Figure 4-5.
The problem here is that Stop
is a keyword in VB.NET. A Stop
statement suspends execution of the code. Yes, you do want to suspend the execution of the StopWatch
, but not the execution of the program. But there is a workaround in this case. You can write the method as shown in Figure 4-6.
This allows the code to compile. When invoking the method on an object of SpecialStopWatch
, you simply call the Stop()
method like you call any other method:
obj
.Stop()
, where
obj
stands for the name of the method.
While VB.NET escapes the conflicting methods by placing the name within []
, C# uses an @
prefix. For instance, say you have a method named override()
in a VB.NET class. If you derive from that class in C# and override the method named override()
, you have to write it as shown in Figure 4-7.
When calling this method, you write obj.@override(). This works, but it’s ugly.
When choosing names (for methods, properties, etc), use caution not to use names that are keywords in other languages. The other languages may have to use escape characters to access these conflicting names. Of course, as more languages become available in the .NET environment, you can’t possibly know all their keywords. But if you know this is an issue, at least it won’t surprise you if you bump into it.
Gotcha #31, "Optional parameters break interoperability,” Gotcha #32, "Mixing case between class members breaks interoperability,” and Gotcha #34, "Defining an array isn’t consistent.”
Most of the application code I write is in C#. However, I have been involved in a few projects where I had to mix languages and write some VB.NET code. The fact that the .NET Framework class library (FCL) is the same for C# and VB.NET gives me the confidence that I can switch between the languages with maximum ease. But my confidence has been shaken when I have come across code that looks correct but doesn’t work the way I expect it to.
When dealing with arrays, quite a bit of confusion is inherited from the earlier languages. While C-derived languages have zero-based indexes, VB6 used one-based indexes. However, in VB.NET, the index is zero-based. Well, can you say that all .NET languages are consistent now?
Suppose you are a C# developer and you write the code in Example 4-9.
Example 4-9. Array allocation
✓C# and VB.NET (Array)
//Test.cs part of C# project Iterateover.exe
using System;
namespace IterateOver
{
class Test
{
[STAThread]
static void Main(string[] args)
{
object[] objects = new object[3];
for(int i = 0; i < 3; i++)
{
objects[i] = new object();
}
foreach(Object anObj in objects)
{
Console.WriteLine(anObj.GetHashCode());
}
}
}
}
You create an array of objects and set each element to an object instance. Then you use foreach
to traverse the array and print the hash code of each object. The output is shown in Figure 4-8.
Now suppose you are asked to convert this code to VB.NET. That shouldn’t be too difficult, right? So, here you go, your VB.NET version of the above code is shown in Example 4-10.
Example 4-10. VB.NET version of Example 4-9
That’s a straightforward port. You changed the syntax from C# to the appropriate syntax in VB.NET. But when you execute the VB.NET code, you get the output shown in Figure 4-9.
What went wrong? It first printed the hash code for the three objects as you would expect, and then it threw a NullReferenceException
. The reason for this problem is that, in VB.NET, when you declared new Object(3) {}
, you actually created an array of size 4 instead of size 3. Consider the code in Example 4-11.
Example 4-11. Array size differences
✓C# and VB.NET (Array)
//MyCollection.cs part of CSLib.dll using System; namespace CSLib { public class MyCollection { public int[] Values { get { return new int[10]; } } } } 'MyCollection.vb part of VBLib.dll Public Class MyCollection Public ReadOnly Property Values() Get Return New Integer(10) {} End Get End Property End Class 'TestModule.vb in VBUser.exe Module TestModule Sub Main() Dim obj1 As New CSLib.MyCollection Dim obj2 As New VBLib.MyCollection Console.WriteLine("Size of arrays") Console.WriteLine("C# created: {0}", obj1.Values.Length) Console.WriteLine("VB.NET created: {0}", obj2.Values.Length) End Sub End Module
In C# you create an array using the syntax new int[10]
, while in VB.NET you use new int(10) {}
. It looks like you’re doing the same thing in both, but when you run the program you get the output shown in Figure 4-10.
Here’s the crux of the problem: when you create an array in VB.NET, you specify the maximum index value. In C#, you specify the maximum size (one more than the maximum index value).
Use caution creating arrays, and know the difference in how C# and VB.NET declare them. In C#, you specify the maximum size of the array. In VB.NET, you specify the maximum allowed index value.
Gotcha #31, "Optional parameters break interoperability" and Gotcha #33, "Name collision with keywords breaks interoperability.”
3.12.74.189