Troubleshooting a misbehaving designer in Visual Studio 2010

Published by Marco on

This article originally appeared on earthli News and has been cross-posted here.

Anyone who’s used Visual Studio 2010[1] for a non-trivial Windows Forms project has run into situations wherein the designer can no longer be opened. Usually, it’s because the class encounters null-reference exceptions when referencing data that is unavailable until runtime. Those are easy to fix: just avoid referencing that data in the constructor or load-routine while in design-mode.

However, sometimes Visual Studio has problems loading assemblies that it seems it should have available. Sometimes Visual Studio seems to have a devil of a time loading assemblies whose location it has quite explicitly been told.

If you like, there is a walkthrough—with screenshots!—at the end of this article, which shows how to solve even the most intractable designer problems.

A Tale of Two Platforms

One of the troubles is that many developers have moved to 64-bit Windows in order to take advantage of the higher RAM limits. The move to 64-bit causes some issues with many .NET assemblies in that the developer (i.e. probably YOU) didn’t remember to take into account that an assembly might be loaded by x86 code or x64 code or some combination thereof. The designer will sometimes be unable to load an assembly because it has been compiled in a way that cannot be loaded by the runtime currently being used by the designer as explicitly requested in the project settings. That said, the request is explicit as far as Visual Studio is concerned, but implicit as far as the developer is concerned.

The only long-lasting solution is to learn how assemblies are loaded and what the best compile settings are for different assemblies so that you will run into as few problems as possible.

There are several considerations:

  1. It would be nice to have class libraries that can be loaded by any executable instead of having separate versions for x64 and x86.
  2. It would also be nice to be able to benefit from as many debugging features of the environment as possible (e.g. the Edit & Continue feature does not work with x64 builds).
  3. It would be nice to have the most optimal executable for the target platform. (This is usually taken to mean an executable compiled to run natively on the target, but turns out not necessarily to be so, as shown below.)

In order to help decide what to do, it’s best to go to the source: Microsoft. To that end, the article AnyCPU Exes are usually more trouble than they’re worth by Rick Byers (MSDN Blogs) provides a lot of guidance.

  • “Running in two very different modes increases product complexity and the cost of testing”: two different platforms equals two times as much testing. Build servers have to compile and run tests for all configurations because there can be subtle differences.[2]
  • “32-bit tends to be faster anyway”: the current version of the WOW (Windows-on-Windows) runtime on 64-bit systems actually runs code faster than the native 64-bit runtime. That still holds true as of this writing.
  • “Some features aren’t avai[l]able in 64-bit”: the aforementioned Edit & Continue counts among these, as does historical debugging if you’re lucky enough to have a high-end version of Visual Studio.

Given all of the points made above and assuming that your application does not actually need to be 64-bit (i.e. it needs to address more RAM than is available in the 32-bit address space), your best bet is to use the following rules as your guide when setting up default build and release settings.

  • Pure class libraries should always be compiled for “Any CPU” (i.e. able to be loaded by both x86 and x64 assemblies).
  • Executables should always be compiled as x86.
  • Unit-test assemblies should also be compiled as x86 in order to be able to use Edit & Continue.

Where Did You Find That?!

Once you’ve set up your build configuration appropriately and rebuilt everything, you will avoid many design-time errors.

Though not all of them.

Visual Studio has a nasty habit of loading assemblies wherever it can find one that matches your requirements, regardless of the location from which you linked in the assembly. If you look in the project file for a C# Visual Studio project (the .csproj-file), you’ll actually see an XML element called <HintPath> after each assembly reference. The name is appropriately chosen: Visual Studio will look for an assembly in this location first, but will continue looking elsewhere if it’s not there. It will look in the GAC and it will look in the bin/Debug or bin/x86/Debug folder to see if can scrounge up something against which to link. Only if the assembly is not to be found anywhere will Visual Studio give up and actually emit an error message.

At Encodo, we stopped using the GAC entirely, relying instead on local folders containing all required third-party libraries. In this way, we try to control the build configuration and assemblies used when code is downloaded to a new environment (such as a build server). However, when working locally, it is often the case that a developer’s environment is a good deal dirtier than that of a build server and must be cleaned.

Though Visual Studio offers an option to clean a project or solution, it doesn’t do what you’d expect: assemblies remain in the bin/Debug or bin/x86/Debug folders. We’ve added a batch command that we use to explicitly delete all of these folders so that Visual Studio once again must rely on the HintPath to find its assemblies.

If you find yourself switching between x86 and x64 assemblies with any amount of frequency, you will run into designer loading errors when the designer manages to find an assembly compiled for the wrong platform. When this happens, you must shut down Visual Studio, clean all output folders as outlined above and re-open the solution.

Including References with ReSharper

A final note on references: if you adopt the same policy as Encodo of very carefully specifying the location of all external references, you have to watch out for ReSharper. If ReSharper offers to “reference assembly X” and “include the namespace Y”, you should politely decline and reference the assembly yourself. ReSharper will reference the assembly as expected but will not include a HintPath so the reference will be somewhere in the bin/Debug or bin/x86/Debug folder and will break as soon as you clean all of those directories (as will be the case on a build server).

Designer Assemblies

This almost always works, but Visual Studio can still find ways of loading assemblies over which you have little to no control: the designer assemblies.

In all likelihood, you won’t be including the designer assemblies in your third-party binaries folder for several reasons:

  1. They are not strictly required for compilation
  2. The are usually a good deal larger than the assembly that they support and are only used during design-time
  3. Design-time assemblies are usually associated with visual component packages that must be installed anyway in order for a compiled executable to be considered licensed.[3]

For all of the reasons above, it’s best not to even try to get Visual Studio to load designer assemblies out of a specific folder and just let it use the GAC instead.

Walkthrough: Solving a Problem in the Designer

Despite all of the precautions mentioned above, it is still possible to have a misbehaving designer. The designer can be so mischievous that it simply refuses to load, showing neither a stack not an error message, keeping its reasons to itself. How do we solve such a problem?

You know you have a problem when the designer presents the following view instead of your form or user control.

 Designer not loaded

In the worst case, you will be given neither a useful error message nor a stack from which to figure out what happened.

 No reason and no stack

There’s a little link at the top—right in the middle—that you can try that may provide you with more information.

 Roll the dice and load it anyway

The designer will try to scare you off one last time before giving up its precious secrets; ignore it.

 What have you got to lose?

At this point, the designer will finally show the warnings and errors that describe the reason it cannot load.[4]

 Load warnings

The text is a bit dense, but one thing pops out immediately:

 Why are you even looking there?

It looks like Visual Studio is checking some cached location within your application settings to find referenced assemblies and their designer assemblies.[5] This is a bit strange as Visual Studio has been explicitly instructed to load those assemblies from the third-party folder that we carefully prepared above. Perhaps this cache represents yet another location that must be cleared manually every once in a while in order to keep the designer running smoothly.

[A]DevExpress.XtraLayout.LayoutControl cannot be cast to [B]DevExpress.XtraLayout.LayoutControl. 
Type A originates from 'DevExpress.XtraLayout.v10.2, Version=10.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a' 
in the context 'LoadNeither' at location 
'C:\Documents and Settings\Marco\Local Settings\Application Data\Microsoft\VisualStudio\10.0\ProjectAssemblies\kn8q9qdt01\DevExpress.XtraLayout.v10.2.dll'. 
Type B originates from 'DevExpress.XtraLayout.v10.2, Version=10.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a'
in the context 'Default' at location
'C:\WINDOWS\assembly\GAC_MSIL\DevExpress.XtraLayout.v10.2\10.2.4.0__b88d1754d700e49a\DevExpress.XtraLayout.v10.2.dll'.

This will turn out to be a goose chase, however.[6] The problem does not lie in the location of the assemblies, but rather in the version. We can see that the designer was attempting to load version 10.2.4.0 of the third-party component library for DevExpress. However, the solution and all projects were referencing the 10.2.5.0 version, which had not been officially installed on that workstation. It was unofficially available because the assemblies were included in the solution-local third-party folder, but the designer files were not.

Instead of simply showing an error message that the desired version of a required assembly could not be loaded, Visual Studio chose instead to first hide the warnings quite well, then to fail to mention the real reason the assembly could not be loaded (i.e. that it conflicted with a newer version already in memory). Instead, the designer left it up to the developer to puzzle out that the error message only mentioned versions that were older than the current one.[7]

From there, a quick check of the installed programs and the GAC confirmed that the required version was not installed, but the solution was eminently non-obvious.

That’s about all for Visual Studio Designer troubleshooting tips. Hopefully, they’ll be useful enough to prevent at least some hair from being torn out and some keyboards from being thrown through displays.

[1] All tests were performed with the SP1 Beta version available as of Mid-February 2010.
[2] One such difference is how hash-codes are generated by the default implementation of GetHashCode(): the .NET implementation is optimized for speed, not portability so the codes generated by the 32-bit and 64-bit runtimes are different.
[3] In the case of SyncFusion, this means the application won’t even compile; in the case of DevExpress, the application will both compile and run, but will display a nag screen every once in a while.
[4] If you’re lucky, of course. If you’re unlucky, Visual Studio will already have crashed and helpfully offered to restart itself.
[5] Then it encountered a null-reference exception, which we can only hope will actually get fixed in some service pack or other.
[6] I tried deleting this folder, but it was locked by Visual Studio. I shut down Visual Studio and could delete the folder. When I restarted and reloaded the project as well as the designer, I found to my surprise that Visual Studio had exactly recreated the folder structure that I had just deleted. It appears that this is a sort of copy of the required assemblies, but the purpose of copying assemblies out of the GAC to a user-local temporary folder is unclear. It stinks of legacy workarounds.
[7] In the case of DevExpress, this didn’t take too long because it’s a large component package and the version number was well-known to the developers in the project. However, for third-party components that are not so frequently updated or which have a less recognizable version number, this puzzle could have remained insoluble for quite some time.