A .NET 4.5 incompatibility

.NET Framework 4.5 is an in-place upgrade for .NET Framework 4.0. What this means is that it's impossible to have both 4.0 and 4.5 installed. If you want to run just a single application on 4.5, you must run all applications written for 4.0 on 4.5 as well. This places some very strong constraints on how compatible such an upgrade must be.

Ever since this was announced, a number of people expressed their concern that the required level of compatibility is not going to be achieved. I'm certainly one of the people who feel an in-place upgrade with so many changes is not such a great idea, to say the least.

Now I'm on 4.5, and I've run into an incompatibility myself. One that makes every single GUI I've built and tested in .NET over the years have a fairly annoying quirk on machines running 4.5. Read on for details.

There's a trusty old message box implementation I've been using for years. Here's what it looked like after the upgrade:

Screenshots before and after

Oooops! My first thought was that this is somehow related to right-to-left scripts. But that wasn't it. After a fairly lengthy elimination of all code not needed to reproduce the behaviour, it turned out that the message box, which uses a TableLayoutPanel, places both the message and the image in cell 0, 0. The documentation doesn't say quite what is supposed to happen in this situation.

Here's a minimal program to reproduce this behaviour:

using System.Drawing;
using System.Windows.Forms;

static class Program
{
    [System.STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        var pnl = new TableLayoutPanel { RowCount = 1, ColumnCount = 2 };
        pnl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
        pnl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
        pnl.RowStyles.Add(new System.Windows.Forms.RowStyle());

        pnl.Controls.Add(new Label { Text = "First!", ForeColor = Color.Maroon }, 0, 0);
        pnl.Controls.Add(new Label { Text = "Second...", ForeColor = Color.Blue }, 0, 0);

        var form = new Form { Font = new Font("Arial", 15) };
        form.Controls.Add(pnl);

        Application.Run(form);
    }
}

The output:

Screenshots before and after

For shame!

Don't get me wrong. Yes, the program relies on a corner case. Yes, the new behaviour makes a bit more sense. Yes, it's impossible to avoid all breaking changes without stifling progress. That's all fine.

The problem is that this upgrade combines the following properties: it adds new features, it changes existing features, and it cannot be installed side-by-side. This is the perfect set-up for a compatibility fail: new programs make use of new features, old programs rely on old behaviours, and you are forced to choose whether to let either just the new, or just the old programs run correctly on your machine.

Of course, the idea was to add new features without changing the behaviour of existing features. But the number of changes was large enough that it was obvious to a lot of people that there is simply no way to achieve a sufficient level of compatibility. Maintaining every quirk in every existing feature when changing that much code is beyond human programming ability.

Observe one thing that upsets programmers about this situation. You can test your code all you want; it works absolutely as expected on .NET 4.0 (and 3.5, and 2.0). To the best of your knowledge, the code is correct. Then, an update gets installed on a machine and code that has been tested to work for years, suddenly breaks. There wasn't much the developer of this code could have done to prevent the occurrence of this problem.

More examples

Since I first posted this, I've encountered the same problem in several other projects in our codebase, and so decided to make an effort and report it on Connect. Let's see if it gets fixed. I'm particularly curious because WinForms is now on the back burner.

.NET 4.5 TableLayoutPanel order swapped
Project 1

.NET 4.5 TableLayoutPanel order swapped
Project 2

.NET 4.5 TableLayoutPanel order swapped
Project 2

.NET 4.5 TableLayoutPanel order swapped
Project 3

Lessons learned

The point I'm trying to get across here is that shared components that gain a number of significant changes must support side-by-side installation with older versions of said components. Microsofties probably know this better than anyone, having significantly alleviated the DLL hell they had on their hands with the introduction of side-by-side assemblies.

Note that this is a general problem that applies to every shared component. If you're doing anything more than a rather careful and isolated critical bugfix, do not upgrade it in place. Things will break. Go the side-by-side route.

The other contributing factor here is the existence of undefined behaviour. If you're an API designer, you would do well to try and eliminate as many cases of undefined behaviour as possible. Either turn them into supported features, or make them explicit errors. Had the duplicate cell position resulted in an exception, this problem would have been eliminated before the very first check-in.

Tags: