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.
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
DistantClassare discoverable using reflection. I’m guessing that there is some transitivity involved here.
- If you change
BaseClassinto 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!
- Never, ever trust Visual Studio when it comes to compiling VB.NET code. Always build with MSBuild outside of the IDE.
- Don’t use VB.NET! Save yourself some time and sanity and use C#!