Friday, February 9, 2007

Form inheritance in Visual Studio 2005

I'm sure many people are familiar with the principles of object-oriented programming such as inheritance, polymorphism, etc. However, not everybody may have considered is the potential for creating a class hierarchy of forms and dialogs. It can be very beneficial to have the ability to extend an existing form class to create a more specific type of form. For example, I constructed a group of C# forms and controls that allow me to design a page as a control and place it in either a wizard or a property sheet. I can just extend my custom page from the base page type and drop on whatever controls I need for that particular page. That's something that I think should have been in the .NET framework, but that's another story.

In any given class hierarchy, it's probable that at least some of those classes will exist purely for the purpose of polymorphism, and they'll be abstract classes. This is no less true with a hierarchy of forms or controls. In my particular example, there's not much use in instantiating an instance of the base page class because it'll have no controls on it, so I marked it as abstract. As I soon found out, the designer in Visual Studio 2005 takes great exception to this.

For some reason, the VS2005 designer tries to actually create instances of these abstract base classes and I'm not quite sure why. If all the abstract methods and properties in the base classes are made virtual and given simple, default bodies, so that the classes can be instantiated, the designer will start working again. It's a real shame; I love using abstract methods in base classes because I find myself often forgetting to implement the derived-class versions of virtual methods like I'm supposed to, and it's nice to have the compiler remind you if you don't. It's just unfortunate that this can't happen if you want the designer to work with your abstract forms or controls.


MyDarkSecret said...

Prefer Composition over Inheritance.

Steve Dinn said...

Ah, the old "is a" vs. "has a" conundrum. You can't really use composition effectively when you're dealing with dialogs. You don't really want a dialog that "has" or "contains" another type of dialog, you want it to *be* that type of dialog.

When a class becomes more tangible and has a visible portion such as a dialog or a window, the "has a" metaphor doesn't apply as easily.

It's all about the polymorphism for me ;)

Brent Rockwood said...

Generally, the .Net framework does tend to favour composition over inheritance, but the Forms and WebForms are the exception - as Steve says they lend themselves to inheritance much more readily.

I agree that it would be nice to mark the abstract bases as abstract. As I understand it, this is due to the fact that the designer actually instantiates an instance of your form at design time. In fact, if you have a bunch of initialization in your constructor, you can actually end up with unwanted state saved in your form definition. Ugghh.

Another similar thing that drives me nuts is that XmlSerializable classes must have a default constructor whether this is appropriate for the design or not. But that's another story...

MyDarkSecret said...

Not quite. "is-a" vs. "has-a" is a trite catch-all rule for composition vs. inheritance, but in fact, composition is largely viewed as a better technique over inheritance for anything but the most trivial object models. GUI models are absolutely non-trivial. With inheritance you run into problems with conflicting base-classes especially in C++ (the "diamond" problem). All problems of multiple inheritance can be solved with composition. In this particular case, your problem would go away since the designer *could* create an instance of the class in question. The problem would be that you would have to delegate about 50+ methods that belong to the Window base-class. A Facet pattern (or even a Decorator pattern) would fix this problem much better, but obviously M$ still hasn't chosen a good object model for their GUI. You could probably handle this easily by using introspection and creating a dynamic proxy object for methods that you don't want to override. Actually, wouldn't the C# "delegate" option do this for you?