| Problem |
Example |
Best addressed at migration step...2 |
Recommended approach |
| Before |
During (via tool) |
After |
| Accessing a static member using an instance reference |
Me.SomeStaticMethod() instead of SomeType.SomeStaticMethod() |
x
(VB 2005+) |
|
x
(VB 2003) |
This is only really a problem if you are using VB 2003 since the more recent VB compilers are capable of issuing a compiler warning for this scenario. If you are using VB 2003 and are planning both a C# migration and a .NET Framework version upgrade, you might want to consider performing the .NET upgrade first so that you can more easily clean up the inappropriate references before your C# migration. |
| Accessing a member of a VB module without a type reference |
SomeModuleMethod() instead of SomeModuleType.SomeModuleMethod() |
x |
|
|
Replace all VB modules with classes containing static members before performing the C# migration. We used a custom FxCop rule to help us find all the modules. |
| Declaration of methods with optional parameters |
Public Sub SomeMethod(Optional ByVal something As Integer = 0) |
|
x |
|
Once you decide to migrate to C#, start using overloads instead of creating new signatures with optional parameters in VB. (FxCop includes a rule, DefaultParametersShouldNotBeUsed, that can help you detect any methods with optional parameters.) It shouldn't be necessary to manually convert existing signatures with optional parameters to overloads if you're using a conversion tool like Instant C#. |
| Use of methods with optional parameters |
foo.DoSomething(a, , c) |
|
x |
x |
There are two cases in which this can cause problems. The first is when the method with optional parameters is not defined in your own code base, so the signature with optional parameters is not replaced with overloads at conversion. The good news is that most third party librairies you use were probably written in C#, so they are unlikely to use optional parameters. However, if you reference internal libraries, they are somewhat more likely to have been written in VB. We addressed this issue by starting our C# migration at our "lowest" level of dependency and working "upwards" so that no given VB project is migrated to C# before all its internal dependencies have been migrated.
The second scenario where invocation of the methods with optional parameters can cause problems is when there are gaps in the sequence as in the example to the left. We encountered so few of these that we simply fixed the problems after migration with Instant C#. |
| Use of named parameters |
foo.DoSomething(someArgument:=a) |
|
x |
|
We didn't encounter any problems with this so either we weren't using named parameters in method invocations, or Instant C# took care of them with no problems. |
| Implementation types nested inside interfaces |
See rule #1 at http://msmvps.com/blogs/calinoiu/archive/2007/06/03/some-fxcop-rules-for-vb.aspx for examples |
x |
|
|
All implementation was moved out of interfaces prior to the C# migration. Unfortunately, finding these problems before migration can be a bit tricky (particularly for the case where the nested type is a delegate created by the VB compiler for an event declared on the interface), so we created an FxCop rule to find them for us. |
| Declaration of properties with arguments |
Public Property SomeProperty(ByVal foo As String, ByVal bar As Integer) As String |
x |
|
|
These were converted to methods prior to the C# migration. We used a custom FxCop rule to help us locate the problem properties (rule #2 at http://msmvps.com/blogs/calinoiu/archive/2007/06/03/some-fxcop-rules-for-vb.aspx). |
| Properties named "Item" are only recognized as indexers if they are also marked as the type default member |
Public Property Item(ByVal index As Integer) As Object |
x |
|
|
Where applicable, Item properties were marked as default prior to the C# migration. We used a custom FxCop rule to help us locate the problem properties. |
| Use of MyClass for call scope forcing |
MyClass.DoSomethingVirtual() instead of Me.DoSomethingNonVirtual() |
x |
|
|
These were addressed prior to migration. We used a custom FxCop rule to help us locate the problems (rule #4 at http://msmvps.com/blogs/calinoiu/archive/2007/06/03/some-fxcop-rules-for-vb.aspx). |
| Member name matches type name |
A property named Label declared on a class named Label |
x |
|
|
These were addressed prior to migration. We used a custom FxCop rule to help us locate the problem members (rule #3 at http://msmvps.com/blogs/calinoiu/archive/2007/06/03/some-fxcop-rules-for-vb.aspx). |
| Case mismatches |
Imports SomeNameSpace instead of Imports SomeNamespace |
x |
|
x |
We fixed as many as possible of these before migration by running test migrations and fixing the problem in the original VB source code as well as the migrated C# code. |
| Conditional compilation expressions (as opposed to straight-up use of compilation constants) |
#IF SOME_VERSION = 1 instead of #IF SOME_VERSION_1 |
x |
|
|
These were fixed prior to conversion, preferably by moving the code in question to a method marked with ConditionalAttribute. |
| Use of C# reserved words for names |
Dim switch As Boolean |
x |
|
x |
These were addressed immediately after conversion by prefixing the variable name with the "@" character if no other non-breaking renaming were possible. |
| Use of ReDim |
|
x |
|
x |
Where possible, the code was modified prior to conversion in order to avoid the ReDim. In cases where it couldn't be easily avoided and Instant C# reported conversion problems, Reflector was used to generate a C# version of the original VB code. |
| Inappropriate use of the Shadows keyword |
|
x |
|
x |
There are two problems here. The first is that quite a few developers seem to automatically smack on a "Shadows" rather than an "Overloads" when they see compiler warning BC40003 ("<type1> '<membername>' shadows an overloadable member declared in the base <type2> '<classname>. If you want to overload the base method, this method must be declared 'Overloads'."). The second problem is that the VB compiler sometimes requires members to be marked with Shadows even when they're not really shadowing anything.
We fixed at least some of the first category in the VB source code when running test conversions, but we had to wait until post-conversion to fix the latter category. |
| Use of partial namespace names for qualifying types |
Dim x As B.C instead of Dim x As A.B.C |
x |
|
x |
The VB and C# compilers seem to use different prioritization rules when resolving partial namespaces. To avoid the problem, we started requiring use of complete namespace names (preferably via Imports) as soon as we discovered the issue. We also cleaned up as many instances of the problem as possible in the VB source code during conversion test runs. |
| Class name starting with an underscore is not CLS-compliant |
|
x |
|
|
This is detected as a compiler warning by the VB 2005 and VB 2008 compilers, so upgrade before migrating to C# if possible. Otherwise, smack on [CLSCompliant(false] attributes after migration for minimal code churn unless you're dealing with abstract classes or members, in which case fixing the problem before migration would probably be a good idea. |
| Implicit conversion of enum argument values to integers by VB |
|
|
|
x |
When the VB compiler encounters an invocation of a method with an integer argument in which an enum value is provided instead of an integer value, the compiler will automatically substitute the integer value even if there is an overload that would take a boxed enum value. Sometimes you're lucky, and a compilation error will be seen in C#. In other scenarios, you'll only see a change in runtime behaviour (e.g.: try running Console.WriteLine(AttributeTargets.Class) from VB and C# code).
We briefly considered trying to create an FxCop rule to detect the problem cases, but we weren't sure that we had discovered all the scenarios that would lead to different behaviours between the two compilers, and we ended up fixing the issues by brute force instead. Luckily, we had reasonably good test coverage to help us find problems. Your mileage may vary. |
| Comparisons to empty strings |
If someString = String.Empty Then... |
x |
|
|
Replace all comparisons to empty strings by explicit verification of the nullity of the string and comparison of the string length to 0 (using String.IsNullOrEmpty where appropriate).3 The main reason for this is that at least the VB 2003 compiler will force use the Microsoft.VisualBasic.CompilerServices.StringType.StrCmp() function to compare strings, and that function treats nulls like empty strings. |
| Use of CBool or CType(<string>, Boolean) to convert strings to boolean values |
CBool("1") |
|
|
x |
The Microsoft.VisualBasic library methods that implement these VB-specific functions will convert string representations of numbers to boolean values based on whether they represent zero or not. A cast to boolean in C# will not do this. A call to System.Convert.ToBoolean will result in a FormatException in .NET 1.1 but not in .NET 2.0 (at least as of SP1), where the "true if not a representation of zero" logic seems to be applied. |
| Reflection on inherited non-public members |
|
|
|
x |
The VB and C# compilers sometimes apply slightly different metadata to generated IL. One side effect of this is that an inherited non-public member can be accessed directly as if it were part of a subclass if it comes from an assembly compiled from VB code, but not if it comes from an assembly compiled from C# code. If your application relies on reflection to find a non-public inherited member, the code that is responsible for finding it may need to be modified to look for the member in base types until it finds a match or runs out of base types. |
| Type and member organization in compiled assembly |
|
x |
|
x |
If you generate assemblies via the Reflection.Emit approach, and you're not already enforcing verification of the emitted assemblies, you might want to start right away. Of course, there are excellent reasons for this beyond the consequences of a VB to C# migration. However, if your emitted assemblies worked just fine when they referenced your VB-based assemblies, you may find unexpected differences in behaviour after migration of those referenced assemblies to C#. PEVerify can help you find such problems early. |
| Embedded resource paths |
|
|
|
x |
The VB and C# compilers use different rules for generating the full names of embedded resources, particularly with respect to files that are not at the project root. You may find it necessary to move an embedded resource file into the project root folder if you cannot easily identify and change the places where an embedded resource path is specified in the code. |
| String manipulation functions |
|
x |
|
x |
VB string manipulation functions like Left, Right, and StrRev have somewhat different behaviour than their System.String method analogues. For example, Left("abc", 4) returns "abc", but "abc".Substring(0, 4) throws an ArgumentOutOfRangeException. Unless you find this change in behaviour to be desirable, you may want to replace your use of the VB functions by custom methods that conserve the VB behaviour rather than switching to the System.String methods. |
| Integer vs non-integer division |
Console.WriteLine(1 / 2) |
x |
|
x |
C# defaults to using integer division if both arguments are integral numeric types. VB always uses real number division rules. So far, we've only converted DevLogic assemblies and FinLogic test assemblies, which luckily don't perform a lot of division, so we simply performed a manual review of all division operations. (If you're going to do this, search for division in the VB code, where the slash character isn't a comment marker.)
Unfortunately, our FinLogic projects, being chock full of financial analysis code, contain a heck of a lot more division operations. In order to ensure that we find all non-integer division cases that would be converted to integer division, we'll probably need to create a custom FxCop rule to find the division problems before we can migrate those assemblies. |
| Equals and GetHashCode |
|
x |
|
|
In VB, one can override the Equals method without overriding GetHashCode. In C#, this generates a compiler warning. In practice, you may find that most cases where you've overridden Equals without also overriding GetHashCode are generally mistakes, and removing the Equals override may be the best fix. Otherwise, add an appropriate GetHashCode override before the C# migration. The FxCop rule OverrideGetHashCodeOnOverridingEquals can help you find the problems. |
| Event handling wire-up code |
|
|
x |
|
C# has no analogues for the VB WithEvents or Handles syntax, which means that a relatively complex conversion with auto-generated class members will be necessary to migrate VB code that uses these keywords. If you view the C# representing the IL generated for such code via Reflector, you'll see what's necessary to successfully migrate the code. Unfortunately, this is one of the weaker areas of Instant C#, and we ended up manually pasting code from Reflector for the few problems that we encountered with the conversions we've done so far. However, we do have one remaining DevLogic project that we've been delaying migrating simply because of the sheer quantity of WithEvents and Handles uses that it contains, so poor Dave at Tangible is probably going to be getting yet another feature request from us in the near-ish future... |