Tricked by the IDE, broke the build

Posted on Tue 18 September 2012 in Coding

Over the last year, I’ve written a fair amount of Visual Basic .NET code for a customer project. As a result, I know a lot more VB.NET than I would like to, and I’ve run into a number of peculiar behaviors, such as this one. In this blog post, I will tell the story of when Visual Studio tricked me into breaking the build.

This was .NET framework 4 and Visual Studio 2010, so nothing too ancient at all.

In short, this is what happened: Within one project, I wrote a class that inherited from a generic class, specifying a class from the System.ServiceModel.Activation namespace as type argument. I then used the sub class from a different project, adding the first project as reference. Building the solution in Visual Studio resulted in a big, happy “Build success!” I ran the tests, watched them pass and them committed everything to the Subversion repository. Then I watched in horror as the build failed on the build server:

error BC30007: Reference required to assembly ‘System.ServiceModel.Activation, …’ containing the base class ‘System.ServiceModel.Activation…’. Add one to your project.

Wat?

So essentially, MSBuild failed the build with a compile-time error that Visual Studio somehow didn’t encounter! How did that happen?

Here’s some code that illustrates the problem:

' Class in a "distant" project (C).
Public Class DistantClass
End Class

' Base class in a Visual Studio project (B).
Public Class BaseClass(Of T)
    Public Overrides Function ToString() As String
        Return "All your base belongs to us! " & GetType(T).Name
    End Function
End Class

' Sub class in the same project as BaseClass (B).
Public Class SubClass
    Inherits BaseClass(Of DistantClass)
End Class

' Ultimate consumer, in its own Visual Studio project (A).
Public Class AnyClass
    Shared Sub Main()
        System.Console.WriteLine(New SubClass())
    End Sub
End Class

As indicated by the comments, the classes belong to three different projects, with the following structure:

+----------+       +-----------+       +------------+
|     A    |       |     B     |       |      C     |
| AnyClass | ----> | BaseClass | ----> |DistantClass|
|          |       | SubClass  |       |            |
+----------+       +-----------+       +------------+

If you create this project structure in Visual Studio, your solution will build successfully. Now, open up a console and type (with XX being the solution name, of course):

msbuild /t:Rebuild XX.sln

BOOM! The build fails with the aforementioned BC30007 error! MSBuild wants us to add a reference from A to C:

+----------+       +-----------+       +------------+
|     A    |       |     B     |       |      C     |
| AnyClass | ----> | BaseClass | ----> |DistantClass|
|          |       | SubClass  |       |            |
+----------+       +-----------+       +------------+
     |                                        ^
     +----------------------------------------+

The problem has nothing to do with MSBuild. The error message comes from the VB compiler. Try the following command sequence to see it:

vbc /t:library /out:C.dll DistantClass.vbvbc /t:library /out:B.dll /r:C.dll BaseClass.vb SubClass.vbvbc /t:exe /out:A.exe /r:B.dll AnyClass.vb

To be honest, I haven’t been able to figure out exactly why the reference is required, only that Visual Studio and the compiler have different dependency resolution strategies. What I have found in addition to this is the following:

  • The C# compiler does NOT need the reference from A to C in a corresponding C# setup. With the exception of VB-specific stuff, the final C# assembly is pretty much identical to the corresponding VB assembly. Apparently, the C# compiler is smarter than the VB compiler.
  • The presence of and information about DistantClass are discoverable using reflection. I’m guessing that there is some transitivity involved here.
  • If you change BaseClass into a non-generic class that inherits from DistantClass, you will get the C# equivalent of BC30007, CS0012:

error CS0012: The type ‘DistantClass’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘C, …’.

Apparently, inheritance requires more information that a constructed generic type!

In conclusion:

  1. Never, ever trust Visual Studio when it comes to compiling VB.NET code. Always build with MSBuild outside of the IDE.
  2. Don’t use VB.NET! Save yourself some time and sanity and use C#!