Chapter 4. Language Interoperability Gotchas

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.

GOTCHA #30 Common Language Specification Compliance isn’t the default

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.

IN A NUTSHELL

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.

SEE ALSO

Gotcha #31, "Optional parameters break interoperability.”

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

C# and VB.NET (Optional )

 
'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.

C# IntelliSense for a method with optional parameter

Figure 4-1. C# IntelliSense for a method with optional parameter

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

C# and VB.NET (Optional )

 
//SomeClass2.cs part of CSLib.dll
using System;
 
namespace CSLib
{
    public class SomeClass2 : AVBDNLibrary.SomeClass1
    {
        public override void SomeMethod(int val1, double val2)
        {
            base.SomeMethod (val1, val2);
        }
    }
}

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.

Error compiling code in Example 4-3

Figure 4-2. Error compiling code in Example 4-3

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).

Error compiling code in Example 4-4

Figure 4-3. Error compiling code in Example 4-4

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.

IN A NUTSHELL

Not all .NET languages support optional parameters. This may lead to interoperability problems. Consider using method overloading instead of optional parameters.

GOTCHA #32 Mixing case between class members breaks interoperability

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.

Example 4-5. Mixing the case

C# and VB.NET (DifferInCase )

 
//SomeClass.cs part of CSLib.dll
using System;
 
namespace CSLib
{
    public class SomeClass
    {
        public int Value1
        {
            get { return 1; }
        }
 
        public int value1
        {
            get { return 2; }
        }
    }
}

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.

Error compiling code in Example 4-6

Figure 4-4. Error compiling code in Example 4-6

Tip

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.

IN A NUTSHELL

Do not write properties, methods, or fields whose names differ only by case. This breaks interoperability.

GOTCHA #33 Name collision with keywords breaks interoperability

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.

Example 4-7. Method name conflict

C# and VB.NET (WhatsInAName )

 
//StopWatch.cs part of CSLib.dll
using System;
 
namespace CSLib
{
    public class StopWatch
    {
        public virtual void Start() {}
 
        public virtual void Stop() {}
 
        public virtual void Reset()
        {
        }
 
        public int ElapasedTime
        {
            get { return 0; }
        }
    }
}

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.

Error compiling the class in Example 4-8

Figure 4-5. Error compiling the class in Example 4-8

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.

Workaround to override Stop() method

Figure 4-6. Workaround to override Stop() method

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.

Keyword prefix in C#

Figure 4-7. Keyword prefix in C#

When calling this method, you write obj.@override(). This works, but it’s ugly.

IN A NUTSHELL

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 #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.

Output from Example 4-9

Figure 4-8. Output from Example 4-9

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

C# and VB.NET (Array )

 
'Test.vb - VB.NET Port of Test.cs
 
Module Test
 
    Sub Main()
        Dim objects() As Object = New Object(3) {}
 
        For i As Integer = 0 To 2
            objects(i) = New Object
        Next
 
        For Each anObj As Object In objects
            Console.WriteLine(anObj.GetHashCode())
        Next
    End Sub
 
End Module

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.

Output from Example 4-10

Figure 4-9. Output from Example 4-10

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.

Output from Example 4-11

Figure 4-10. Output from Example 4-11

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).

IN A NUTSHELL

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.

Get .NET Gotchas now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.