Last dotned meeting I had quite an interesting discussion with Thomas. He's a trainer with Oosterkamp and teaches Delphi and .net. The both of us have quite a history in Delphi. We talked about the great way you can build object factories in Delphi and we puzzled the way you could do that with C# in .net. In the weekend I took another look at it and found out that you can get pretty close in .net to the elegancy of the Delphi solution.
First I have to stress again the differences between classes and objects. A class is the grouped declaration of some variables and associated code. An object is an instance of a class. Most methods work on the instance data in an object. A static method (.net nomenclature) or class function (Delphi nomenclature) works on the class level. A typical static method is a constructor which creates new objects of its class. A class factory is different from a normal constructor, it is a (in most cases, but not necessarily) static method (of a factory class) which creates new objects. In my opinion a better name for a class factory would be object factory, but the name class factory is most commonly used. A nice thing about a factory is that it can create object of diverse types. Let's take a look at a factory which creates controls on a form. In Delphi you can implement that like this:
type
tControlClass = class of tControl;
type tControlFactory = class
class function ConstructControl(OfType: tControlClass; OnForm : tForm): tControl;
end;
Typical is class off, here you define a type to hold class variables. Something unknown in C#. The factory method accepts a class as parameter. Typical calls to this method would be
tControlFactory.ConstructControl(tEdit, self);
tControlFactory.ConstructControl(tMonthCalender, self);
tEdit and tMonthCalender are classes found in the Delphi class library. The implementation of the ConstructControl method is quite simple
class function tControlFactory.ConstructControl(OfType: tControlClass; OnForm : tForm): tControl;
begin
result:= OfType.Create(OnForm);
result.Parent:= OnForm;
end;
The method receives a class variable and makes a call to the constructor of the class. The constructor of a control in Delphi is named Create, this constructor always needs a parameter. The result of the constructor is a control. What kind of control only depends n the class passed in. The method needs no knowledge at all of all possible types of control, the only limitation is that it should descend (indirectly) from tControl.
This is pretty cool code, imagine this
procedure TForm1.Button1Click(Sender: TObject);
begin
case RadioGroup1.ItemIndex of
0 : MaakControl(tEdit);
1 : MaakControl(tButton);
2 : MaakControl(tLabel);
3 : MaakControl(TRichEdit);
4 : MaakControl(TMonthCalendar);
end;
end;
function TForm1.MaakControl(OfType: tControlClass): tControl;
begin
result:= tControlFactory.ConstructControl(OfType, self);
result.Left := TrackBar1.Position;
result.Top := TrackBar2.Position;
end;
This is all the code you need to populate a form with controls.
The question was if you can do something like that in C#. The Delphi code is based on class variables, C# does not have them. Besides using constructors you can create objects in C# using the CreateInstance method. There is the Assembly.CreateInstance method, this method will create an object (class instance) of a class found in that assembly. There are several overloads of the method but all take a string to identify the class. This approach works but you need a reference to the assembly containing the class and I don't think a string makes the best identifier for a class you can imagine.
There are better variations of the CreateInstance method to be found in the framework. The Activator class has far more interesting overloads. The activator class is used to create new objects or get a reference to an object running remotely, it is a world on itself. Here I will dive deeper into it's GetInstance overload which takes a Type variable as its first variable. Type objects hold all metadata on another object or class. C# has no class variables but the Type class comes pretty close. Take this:
Type t =
typeof(TextBox);
The typeof operator extracts all metadata of the argument passed, which is now in the t variable. Here I passed the TextBox class to the operator and now I have an object describing a TextBox which I can pass to a factory.
public class FactoryClass
{
static public System.Windows.Forms.Control ConstructControl(Type t)
{
// Fiddle here
return Activator.CreateInstance(t) as System.Windows.Forms.Control;
}
}
This method does exactly the same as the Delphi controlfactory we just met, it creates controls of a type determined by the Type variable passed in. The controlfactory can be used the same way.
private void button1_Click(object sender, System.EventArgs e)
{
Control nc = null;
if (radioButton1.Checked)
nc = FactoryClass.ConstructControl(typeof(TextBox));
if (radioButton2.Checked)
nc = FactoryClass.ConstructControl(typeof(Button));
if (radioButton3.Checked)
nc = FactoryClass.ConstructControl(typeof(RichTextBox));
if (radioButton4.Checked)
nc = FactoryClass.ConstructControl(typeof(MonthCalendar));
if (nc != null)
{
this.Controls.Add(nc);
nc.Left = trackBar1.Value;
nc.Top = trackBar2.Value;
}
}
(BTW, I would prefer a select statement to find out which radiobutton is checked but have not found anything like the selecteditem property of the Delphi radiobuttongroup yet in WinForms. Anybody ?)
CreateInstance will create the control using its constructor. In this scenario the default (parameterless) constructor, which is fine for a control. But what if the object to be created has multiple constructors which do need parameters ? Another overload of the CreateInstance method accepts an array of objects as its second parameter
Activator.CreateInstance Method (Type, Object[])
This array of objects forms a signature. GetInstance will search all available constructors for one whose signature matches the objects passed. If there is no constructor available which fits the pattern a MissingMethodException is thrown. In the Delphi code the Delphi compiler checked the correct signature (Create needed a parameter) in C# the runtime does the check. The Delphi factory can only create objects which are based on tControl. The C# factory will create any object described in the type parameter, as long as it can find a constructor which matches the arguments passed in. The typecast using as will set the result to null again if the constructed object was not a Control.
It would be nice to have something more in the C# language to describe types. The Type class is fine but you cannot use it to indicate that you need a more specific type. This is where the class variables in Delphi come in handy.