views:

295

answers:

1

Picture the following situation. I have an XML document as follows,

<Form>
    <Control Type="Text" Name="FirstName" />
    <Control Type="DateTime" Name="DateOfBirth" />
    <Control Type="Text" Name="PlaceOfBirth" />
</Form>

I have an abstract class called Control with a single abstract method called Process which takes a single parameter of HttpRequest. I also have two other classes that are derived from Control called TextControl and DateTimeControl. Both Text and DateTime override the Process method to provide their own implementations.

I also have a Form class which has a Process method taking a single parameter of type HttpRequest, and a constructor which takes a single parameter of type XmlDocument.

A new instance of Form is created and the above Xml is passed in via the XmlDocument parameter (how we get from the string to an XmlDocument is irrelevant). I then call the Process method on the instance of Form I just created and pass in a parameter of type HttpRequest as expected.

All good so far. Now onto the question.

To make the processing of Controls extensible I would like to be able to map Classes to control types.

eg.

Form.RegisterControl("Text", Text)
Form.RegisterControl("DateTime", DateTimeControl)

Within the Process method of Form I would like to itterate over each Control node in the document (how to do this is again irrelevant) and instantiate an instance of the class which matches its type based on the Classes registered by our RegisterControl method. I would be able at this stage to specify that they are derived from Control but could not explicitly specify their type. Because they are both derived from Control I want to call the Process method which I know will be implemented.

Is this even possible? If so how would I go about it?

+4  A: 

(This answer is in some ways two different answers, depending on the meaning of your question. Hopefully one part of it is helpful, anyway :)


Probably the best way is to pass in an argument which is a function which can be used to create the new control at the right time. If you're using C# 3, this is as simple as:

Form.RegisterControl("Text", () => new Text())

Alternatively, you could make it a generic method with two constraints: one for being a control, and one for having a parameterless constructor.

public void RegisterControl<T>(string name) where T : Control, new()

then call it with:

Form.RegisterControl<Text>("Text");
Form.RegisterControl<DateTimeControl>("DateTime");

RegisterControl would have to remember typeof(T) in whatever storage it's using, but at least then it could be reasonably sure that Activator.CreateInstance(Type) would work later on - and you'd have compile-time checking.

Personally I like the flexibility of the first form though - if you're passing in a delegate, that can choose to use a singleton, or perhaps some internal or even private constructor (depending on where it's being called from). You could also use a delegate which took the appropriate data, too:

Form.RegisterControl("Text", data => new Text(data));

You can't express that sort of constructor with a generic constraint, and it would be relatively hard to invoke later anyway.


EDIT: It's possible that both myself and Mehrdad have misinterpreted the question. Do you actually have different overloads of RegisterControl based on the control type? If so, then the only ways of directly calling the right overload at execution time are to either use reflection or use dynamic typing in C# 4.

Another alternative would be to use double dispatch - put a method in the control itself which knows how to register itself with a form. This would be specified in the interface or base class, but overridden in the concrete subclasses. So your code which is currently:

Form.RegisterControl("Text", control);

would become:

control.RegisterWith(Form, "Text");

That could then call the correct overload with no issues.

Basically you need to remember that overload resolution is performed at compile time, but override resolution is performed at execution time - so if you want to make something dynamic, try to approach it with polymorphism.

Jon Skeet
Thanks Jon! The first answer should be correct (you've assumed correctly). I'm having a crack at it now!
Craig Bovis
Thanks Jon, this worked perfectly.
Craig Bovis