Page 1 of 3
In the article Custom Shape we developed a simple example of how to build a class that implemented a graphic that could be instantiated as many times as the graphic was required - but there was a single big problem. The class cannot be used with XAML because it can't support a parameter-less constructor.
The reason is that the shape in question is a tree and five of its properties determine the tree's type, i.e. what it looks like. After the geometry of the tree has been generated then other properties such as its position given by X,Y, Stroke and Stroke thickness can be changed as many times as desired without having to regenerate the geometry. However if any of the five type properties change then the geometry has to be regenerated.
In other words the five tree type parameters are necessary before the instance can be constructed. In most cases this would just mean that the five parameters would be mandatory as part of the constructor. In general object-oriented theory the constructor's job is to create the instance and any resources it needs to be functional. However, the needs of XAML dictate that a parameter-less constructor is required and all properties are set after the constructor has been called.
This means that the constructor cannot always create a fully functioning instance and we either need to find a way to allow it to do so or we have to put off the creation of the instance until all of the essential parameters are available.
Whatever solution we opt for it also has to be compatible with XAML - because this is the reason we need a parameter-less constructor in the first place!
Default and regenerate
The simplest solution to the problem is to set all of the essential parameters to reasonable defaults, either using initialization in the class or in the parameter-less constructor, and then regenerate the resources that the determine each time one of the parameters is set.
Clearly this is an option that will work and it will work with XAML but how practical it is depends very much on how time consuming the resource generation is. For example, suppose one of the resources essential to instantiation was a bitmap stored on a remote server - it really isn't sensible to download it more than once. In the same way, if the geometry of a custom Shape class takes a long time to generate you really don't want to regenerate it every time a single parameter is changed.
Batch property setting
If a class cannot be fully instantiated before a set of properties are initialized one solution is to demand that they are initialized as a batch. This is the BeginInit-EndInit pattern and the ISupportInitialize interface was introduced into .NET to provide a uniform way of using it. The idea is simple.
Any class that needs batch initialization has to implement the ISupportInitialize interface which consists of two methods - BeginInit() and EndInit().
These are placed around a set of property initializations like brackets. Any property initialization that occurs outside of BeginInit and EndInit calls is ignored or throws an exception as you please - the mechanism is very open to different implementations. When the EndInit method is called it can assume that all of the essential parameters have been set or are acceptable at their default values and it can then create the resource needed for the instance to be used.
In some situations you can arrange for there to be only one BeginInit/EndInit block and hence you can stop any changes to the essential parameters after the first initialization. It is clear to see that this approach fits in with the Freezable interface, i.e. the object could also be Frozen by the EndInit method. In other cases you can arrange that the instance can be initialized as many times as required with the EndInit block recreating the resource each time.
From our point of view the most important thing about BeginInit and EndInit is that XAML will call them before and after setting properties. This makes it possible to make resource regeneration efficient even with a parameter-less constructor needed for XAML.
Now let's see how this translates into practice.