A Method to System.Design’s Madness

Brian in Coding | 0 Comments November 14, 2004

System.Design is an assembly that contains design-time classes such as ControlDesigner. The System assembly also contains a System.ComponentModel.Design namespace that contains design time classes. Even System.Windows.Forms contains a System.Windows.Forms.Design namespace. Was Microsoft just incredibly lazy here, randomly spreading classes wherever was convenient, or was there an actual purpose to this madness? And, if there was a purpose, what should you consider when writing your own controls with their own design time logic?

Well, it turns out that we did, in fact, have a scheme in mind when we distributed these classes among different assemblies. Our guiding principle was that design time logic should be kept in a separate assembly from the runtime logic. This has two big advantages. First, we can prevent design time code from bloating runtime logic. We can even enforce this at compile time because System.Windows.Forms has no dependency on System.Design. If we depended on code in System.Design System.Windows.Forms would fail to compile. The second advantage is distribution. You might have noticed that the .NET Framework is not exactly petite. We wanted to enable a “minimum redist” that only contained runtime classes. This would help keep the size of the runtime down, while allowing a larger SDK download to contain design time components like System.Design. In the end, we decided not to trim System.Design out of the runtime redist because there wasn’t enough space savings to justify the extra testing cost.

Well, that explains the presence of System.Design, but it doesn’t do much to explain all the design time classes that live in System, System.Windows.Forms and System.Drawing. It turns out we have a reasonably good reason for that too: header files. Or rather, the lack of them. Managed code doesn’t have header files like C and C++, so code must import the classes it directly uses. If you look at the design time classes we have in the core runtime assemblies you’ll see that they consist of interfaces, attributes and simple helper classes. This allows you to use attributes like DesignerAttribute on your runtime class without importing System.Design.

That would neatly wrap things up if it weren’t for the fact that some design time classes, like type converters, are located in the same assembly as the type they convert. Doesn’t that violate our rule of separating design time from runtime? It would, if the type converters were only used for design time. Type converters are used for a wide variety of runtime applications, however, including data binding. For this reason, we keep the type converters in the same assembly as the types they are associated with.

What are the Rules?

I’ve just given you a background primer on why we put things in specific places, but not much in the way of concrete guidance. Here are the rules we follow when deciding where to put design time classes:

  1. Design time declarations – interfaces, attributes and other things that could be necessary at runtime to declare design time classes through metadata – are placed in the runtime assembly.
  2. Design time implementations – classes like designers and UITypeEditors – are placed in a separate design time assembly. Use the string-based constructors of editor and designer attributes to reference these types in your runtime assembly without actually referencing your design time assembly the runtime project’s references.
  3. Type converters should always be placed in the runtime assembly as they are used at runtime by features such as data binding.

Well, hopefully that makes some degree of sense. If you’re a control author writing reusable controls, you might want to use the same model for your controls. Following these rules can help your controls be smaller at runtime, and therefore perform better.


Comments are closed