Part of my job on the Visual Studio project system lately has been looking at the speed at which Visual Studio opens solutions containing managed language projects (such as C# and VB). As with many things, over time this common operation has gotten slower, and we would like to put some spring back in it’s step since everyone (including us!) have to open solutions all day every day.
As we’ve started to look at the various things that go on when you open a solution, one of the most obvious things we had to look at is something called design-time builds. Design-time builds are probably one of the least known and least understood things that go on in Visual Studio, and yet they often can have a real impact on how fast the Visual Studio UI responds (and not just on solution load!). I thought I’d take a minute to explain what design-time builds are and why you should care about them.
The origins of design-time builds comes from the fact that the Visual Studio build system (aka MSBuild) is not a fully declarative system. That is, you can’t just look at a .csproj file or .vbproj file and immediately understand everything you might want to know about how that project is going to build. In particular, you might not know the following:
- Given an assembly reference in the project file, what assembly on disk is that reference going to actually refer to at compile time?
- Given a XAML file, what is the code that is going to be generated by the XAML compiler at compile time going to look like?
- Given a glob file pattern (*.cs), what files are actually going to be included at compile time?
And these questions aren’t just academic — if you’d like to get a nice Intellisense experience (or all the other editor enhancements that you get from the compiler), then the compiler has to be able to answer those questions before you ever get to compiling your project. If it can’t get those answers, then it can’t see all the code and assemblies that make up your project, and therefore it can’t give you help in the editor.
So, what to do? Well, obviously, if you need to know what’s going to happen in a build, why not just run it? And so you get design-time builds. Unbeknownst to most users, Visual Studio builds your projects a lot more than you think. Potentially every time you make a change to a project that will affect how the project is built, Visual Studio will fire off a design-time build in the background. Now, there are a few differences in how design-time builds work from regular builds. In particular:
- The design-time build doesn’t actually run the C# or VB compiler because it doesn’t need to produce the actual output (i.e. dll or exe). It just needs to know how the build would be run. So instead of, say, calling csc.exe, it will just tell MSBuild to let it know what it would have asked csc.exe to compile without actually compiling it.
- The design-time build runs a slightly different set of MSBuild targets from a regular compile. This allows skipping things that aren’t needed in a design-time build, plus it allows the project system to run some additional targets (if needed) to provide extra information that the project system might need for, say, the Solution Explorer.
Design-time builds have two obvious issues. First, if they’re slow, it’s going to slow down Visual Studio but you won’t be able to tell where the problem is coming from. For example, a common place where slow design-time builds show up is switching between configurations. If you switch a large solution, say, from Debug to Release in Visual Studio, you might notice quite a long pause in which you can’t do anything in the UI. This is usually due to the configuration change kicking off a whole bunch of design-time builds to react to the fact that things in the project now might look different under the new configuration.
The other issue is if a design-time build fails. Since the compiler and the project system rely on design-time builds to accurately understand the contents of your solution, if design-time builds fail, things might start going wrong in the Visual Studio UI. The Solution Explorer might not show all of your files, or it might show them in the wrong place. Intellisense might not show you everything available, or it might show you nothing at all. You might get weird errors in the Error List. Often these kinds of problems can be traced back to a failing design-time build. The problem, as with performance issues, is that there’s no visible indication that that’s what the problem is.
So… Design-time builds are a necessary evil that can really mess things up if they go wrong. What can we do about it? Stay tuned…
You can also follow me on Twitter here.