Background
After writing my last article I decided to revise my personal libraries and standardize things that were not quite prepared for future uses or that usually needed too many per-instance configurations.
I started to write an article about evolutive frameworks but, considering the kind of things I were comparing, I decided to write a new article. I am naming this standard as actionless in comparison to WPF faceless controls.
Important Note
At this moment this article is very theorical. It does not have a sample code, but most of its ideas can be found on my last article, named Converters.
This time I am trying to explore an approach that can be used for many different things. The code from the last article can prove some points explained here, but it is still incomplete compared to the ideas on this one.
Introduction - What is an actionless framework?
In WPF a faceless control means a control that has no mandatory face. It usually has a default face but the control is still considered faceless because it does not depend on its face and, in fact, it is really possible to make a control without creating its default face template, letting such job to the user of the control.
By the same principle an actionless framework is any framework that is supposed to do something but, at the same time, that something may be completely changed at run-time. In this sense, WPF is an actionless UI framework. Surely it does its action of presenting the UI, but if you remove all the templates, it is actionless. So, its basic action (of presenting things) is configured (be it by default or by users at run-time) instead of being fixed (in this sense, actionful).
Thinking about an actionless framework
The idea is highly abstract but I will try to present some examples.
Think about something your framework will do and the kind of information it will need to do its job. Now, instead of writing the actual implementation to do it, think about giving an way to register the information needed and the action to be done at run-time.
It will be good if you give a default action too, but don't start by it. Let its very basic action be completely replaceable.
Examples?
-
WPF data-templates. The purpose of a data-template is to visually present data. But if you don't create a data-template, the ToString() of the data is displayed.
So, in a sense, WPF data-templates can be seen as actionless data visualization framework.
-
Record Editors. You may want to edit entire records. This is not the same as a data-template as the data-template already exists and, well, only displays the data.
You may simple have a method like RecordEditor.Edit(someRecord). But, depending on the type of the record, the actual editor is completely different.
-
Serialization. You are not satisfied with the actual serialization mechanisms (I am not) and you decide to create your own. But instead of writing a serializer capable of dealing with all the existing types, you simple let those serializers to be registered at run-time.
As said before, you can create your own defaults. So you add the default to serialize the primitive types. But, even those can be replaced if the user wants to.
-
Conversions. Well, conversions were the reason I wrote my last article, which gave me the inspiration to follow the pattern and then to write this article. What can I say? The information needed to find a conversion is not a single type like on the other cases, but both the input and output types.
-
There are probably a lot of them, but I hope I gave the idea.
Advantages of actionless frameworks
Simple put, actionless frameworks are extremely prepared for future changes and can make your application behave better/look better by a simple new configuration.
If you are using it to edit records, then if in the future you create a better editor, you can simple replace the editor registration and the new (and I imagine better) editor will be used in all places. There is no need to search all the calls to the old editor to replace it by the new one.
If you are using it to convert data and now you have a faster algorithm, simple register the faster algorithm and benefit from better performance.
For serialization? It's the same. In this particular case you will need to take care not to register an incompatible serializer if it should read old data, but that's a problem we should always need to worry about when changing formats.
Creating your own actionless framework
An actionless framework can be seen as an extreme application of IoC (Inversion of Control), Loose-coupling, SRP (Single Responsibility Principle) and probably other principles.
In technical details it will be usually start as a dictionary of kind of information needed and item capable of doing the action.
- Kind of information needed: It is important to understand that kind of information is not the actual information. For example, to a WPF data-template or to a record-editor, the kind of information is the
Type
of the data or the record. A typeof(string)
is the information by itself.
- Item capable of doing the action: For controls we usually create them, so a
Func<T>
will be enough. After they are created you may want to have some information about the controls and want them to follow some interface. But for the serialization process the item can be the serialization or deserialization delegate directly. In my personal implementation I tied the serialization and the deserialization. After all, everything that is serialized should be deserialized by a compatible algorithm. But that's a personal decision. It is possible to untie them to have smaller objects and give the compatibility problem to the user.
So, which that dictionary and with methods to allow users to register their informations and items capables of doing the actions, we will be done.
Done...?
Hummm... isn't something missing there?
OK. That's enough to be done on a non-professional level. It will work for specific projects but it is not a full featured actionless framework yet.
Surely you can implement some default actions, making it do something despite the actionless name, but that's not why I am saying it is not complete. We must think about were such actionless framework could be used and what are the possible problems it may face.
I will return to my last article again. In there I talked about global and local configuration, about multi-threading problems and I even put a Searching
event. So, let's understand those points so you don't need to go to the previous article.
Global and Local configuration
Let's check WPF again. You can put a data-template in the App.xaml and it will be valid for your entire application. You can then put a different data-template for the same data-type at your Window.Resources and that data-template will be used in that window instead of the application level one.
You can use a 3rd-party library that has a dialog, without any templates, and that dialog will use your application templates, without you requiring to change that 3rd party library.
That is: You have global and local configurations.
Is there something still missing?
Well, I will ask you: How do you open that 3rd party library dialog and make it use different templates if the library itself does not give you access to the dialog resources or a specific method to do so?
To me that's a good question and I don't know if that's possible in WPF. We will return to that later, when I give my answers.
Multi-threading
In WPF that's easy. Only the thread that creates an object can change it. That makes most of the things mono-threaded. But, if that was not the case, should the global configurations be thread-safe?
Searching
For some reason the initially configured framework is missing some actions to be done (in WPF, some data-template, for example). Should we simple give an error/execute the default action or should we give a last chance for getting the right action to be done?
My Personal Answers
Try to make the framework complete.
This goes into the opposite direction of the Agile methodologies but if we want our framework to be long living without requiring changes we should let it adapt at run-time to the most different situations.
In fact, I tried to make a generic framework that fill all the needs and could be used as the base to anyone wanting to create their own actionless framework. But a generic framework has the problem of using generic names, and redirecting from a user visible framework to a generic framework didn't reduce the code that much, so I end-up making copies and adjusting the needed items (there is no download at this time, but I am already writing some of those frameworks and testing them, in the future I should put downloads to this article).
But let's see the idea on how to do it:
Global Configurations
To me, they should exist and they should always be thread-safe.
Maybe you think that configuring things per instance is the best solution. But users will end-up creating their own Create
methods, will usually copy the code to initialize objects (which can create bugs when the initialization changes, but not at all places) or, worse, will simple give up on the library.
On the other hand you may think that no-one is going to change a global configuration after the application is initialized (maybe you simple ignore multi-threading or make the configuration read-only).
But your users can put some code to initialize the framework on their static constructors (for example, register the editors for the actual type), but their classes are only loaded after the application is running (which is a normal .Net lazy loading) and, if you already have threads running, boom, you get exceptions because your code is not thread-safe or because the object is not changeable anymore.
Local Configurations
My approach on local configurations was to use a thread-static instance to hold the local configurations. As it can be replaced, it can become a context-specific configuration. In this case, I can replace the configuration just before calling a method and then return to the original.
This is not complete, though. Look at a window. I change the actual configurations, I create the window and then I restore the old configurations. But the window is still there. It can now need to create a new control and it will use the wrong configuration.
So, yes, having a thread-specific configuration is still good as the default one for the thread, but it should allow hierarchies too. In a window, I may want to use one configuration for one panel and another configuration for another panel.
My solution is to allow the creation of new "configurations" and tie them to a parent configuration, to the global configuration or to be isolated. This does not make them the thread default configurations, but a simple call to UseAsLocalInstance
will make them the default. I chose the name Use to remember that you can call it with a using clause, so after the context in which it executes is finished, the old configuration is restored.
The Last Chance Before Failing - Searching event
Pre-configuring an application can be hard or even time-consuming. Maybe the user created a template that is valid for all sub-types of a given type, but the childs of that given type are not all available at load-time. Maybe they are even created during the application execution using Reflection.Emit
.
So, how will the user configure all the valid actions of the framework?
As many things, there can always be work-arounds, but why not give a last chance to register a valid template as part of the framework?
It is not the job of the creator of the framework to know all the possible reasons why the configuration couldn't be done at startup. But by simple giving an event we can solve the problem. So, why not?
Also, if you think it is easy to deal with sub-types directly on the framework, well, I already did a big code to deal with interfaces, sub-interfaces, generics and so on. That creates another level of complexity that will probably not solve all the problems and will make things slower. So, let the user deal with those extra special cases and avoid such complexity from the framework.
At this point I think WPF misses something, or I don't understand how to do things in WPF. I would really love to create a data-template for any IEnumerable
that uses a ListBox
to display the items. As each item will actually use the data-template for the item itself, as long as such item has a data-template it will work. But, if I create a data-template for the IEnumerable
type it will not work. Pre-registering the same data-template for all possible IEnumerable
final types (like string[]
, List<string>
, int[]
, List<int>
and so on) will take a long time and will consume lots of memory.
My actual work-around is to use a data-template selector, but then I have to fill that data-template selector everytime.
In the first version of this article I said it is not hard to make the local/global interaction. Well, I was wrong.
I explained it like this: If an specific item is not found locally, search the parent. If there is no parent but the config is not isolated, search globally.
The idea is good and works very well when we don't have the Searching
event. With such event we must decide the order in which things happen.
For example, should we execute in this order?
- Search actions registered locally;
- Search actions registered in the parent;
- Search actions registered globally;
- Execute local Searching;
- Execute parent Searching;
- Execute GlobalSearching.
Or should we execute in this order?
- Search actions registered locally;
- Execute local Searching;
- Search actions registered in the parent;
- Execute parent Searching;
- Search actions registered globally;
- Execute GlobalSearching.
At the first moment, I didn't though about it but my implementation worked like the second case. The reason was that each level tried to find a registered action, if it was not found executed the Searching
event and, if it was not found yet, asked the parent level to do the same. This also made the global lock to only happen at the last case, which is good for performance.
But I had a problem, I wanted to register a new local Searching
that generates "default" values if no other value is found. But, as local Searching
executes before the parent ones, it would end-up generating too many default values.
I even though about changing the order to the first case but that will not really solve the problem. Should I change the Searching
logic completely, first searching for directly registered items from local to global and then executing the Searching
event from global to local?
Even if that solution will solve my actual problem, it will not be a complete solution because a local configuration could be adding a Searching
handler to replace the result of a GlobaSearching
. If I did such change, only results not generated by a parent Searching
will be functional.
I then though about putting priorities on the Searching
event and so execute things like this:
- Search locally;
- Search the parent;
- Search globally;
- Order all searching handles and them execute them in order.
This again solves my actual problem but creates another problem. What if a local Searching
, with the highest possible priority, wants to replace a fixed action registered on a parent? As the action will be found on the parent it will not need to call the event and the priority would be useless.
AfterSearching
Another option that I though was to create an AfterSearching
event, so I could keep the actual logic and, after executing the GlobalSearching
, could call it, in the reverse order (from the parents to the child).
This will definetely work for my situation and I don't see it creating new problems. But I personally don't like "After" events, specially when the after is related to another event and not to a method.
My solution?
After trying one solution and the other, trying to map the possible problems, I decided to do a very simple thing.
Before, the Searching
event-args was only giving the Manager
(the configuration) that started the operation, independent if the Searching
handler was in a parent or not. By simple putting the actual Manager
in the args, the handler is free to check if a valid result exist in the parents and, if not, give his own result.
In this case, I don't have extra events. The local Searching
will still execute before but, when it executes, if it should not replace actions generated in parent configs, it can check if the parents have a valid result and only give its own when the parents don't have one. This eliminates the complexities and problems of priorities, keeps local to global order and solves my problem. In fact I see it a little more powerful than AfterSearching
as the handlers are getting more information.
Where can we use actionless frameworks?
I will say almost everywhere. If your classes are implementing an interface to give them extra actions, maybe it is the moment to use an actionless framework.
Seriously, if you have a class implementing two or more interfaces you can probaly create this kind of solution and allow two classes to exist, one of them giving more actions to the other class.
Too abstract? Ok, let's see some examples:
ICloneable
You implement ICloneable
in your class so their instances support deep-cloning in a standardized way.
But now think about all those classes that can be deep clonned and don't support it. Probably if you want a generic solution to deep clone objects, looking for that interface will be only one of the options.
So, my solution is to create an Actionless Clonning framework. As ICloneable
already exists, one of the actual Searchers
can call this interface. But this framework will allow you to add deep clone support to other already existing classes.
In fact, if you don't support ICloneable
by default you can even correct a bug caused by that interface. What happens if class A
implements ICloneable
, then class B
inherits from A
but does not override the Clone
method? You will end-up with a Clone
method in class B
, that will generate an A
instance! Big flaw.
IConvertible
Again conversions. Conversions go further on the problem. Different from ICloneable
, where a type A
generates a type A
instance and a type B
generates a type B
instance, IConvertible
is a single interface that tries to generate all kinds of conversion... and it will definetely always miss some.
An interface like: IConvertible<T>
where T
is the destination type will make more sense, as you would be able to tell exactly what kinds of conversion your type will support.
But again, why not putting that logic somewhere else and letting them to be added at run-time and for types that you simple weren't aware of?
ISerializable
I am always returning to the old points. I already talked about serialization and conversions.
But think about it. If your type only uses already serializable types it is enough to mark it as [Serializable]
and the default serialization logic is used. But it you want to provide your custom logic you should implement the ISerializable
interface.
Isn't better to put this logic in another class? And simple tell where is the default serializer class by an attribute or in the static constructor?
Also, by simple doing that we are already letting the code work with singletons (which are a mess in the actual .Net serialization process).
Equality Comparison
This one is the worst one in my opinion.
The object
type, the base off all .Net types, has the Equals
method. As it receives the parameters as object
, it does boxing for value-types.
Later, the IEquatable
interface was added. With it a cast or a boxing/unboxing can be avoided, but you should know the real type of the object to use this one.
To make things nicer, there is the EqualityComparer
delegate and the Default
instance, which will use the IEqualityComparer
if it is available, otherwise it will call the normal Equals
.
All of those are great but they have a single objectif: Make equality comparisons functional, standardized and fast whenever possible.
Now create two lists of the same type (like List<int>
), you can keep them empty. Check their equality with the Equals
method. Are they equal?
The answer is no. LINQ has the SequenceEqual
method to try to solve this case.
But considering that Equals
has the objective of comparing content equality, I consider that the Equals
of any collection should work.
Or, better yet, Equals
should not be put at the object
type. If we had the IEquatable
interface only it will be easier to identify which types support equality comparison and which types don't support it. Also we will solve the inheritance problem. A type A
can implement equality comparison to type A
. But type B
, if it inherits from A
and does not implements IEquatable<B>
will not support equality between B
instances, even if it can still compare with A
(it will only compare its A
part... that may be ok... may be not, but with the default Equals
we have no clue).
But, either way, List
does not support equality comparisons with another List
. The same is true for arrays (and I think that it is true for all standard collections).
In the past, when doing the most generic comparisons I usually used object.Equals
. Then, I created a method to check if the items where IEnumerable
and, if they where, I used the SequenceEqual
, otherwise the old object.Equals
. I also created EquatableLists
s, arrays and so on.
But why not create a solution that can support all kinds of content equality, even those that were not done by default?
In my opinion the object
type is already too poluted. It tries to give responsibilities that are not part of every type responsibilities and that's why many types don't implement Equals
, GetHashCode
and ToString
.
We can't correct the .Net itself but we can create a solution that we use everytime instead of the .Net one. And such solution, if needed, can be expanded to catch those cases the .Net itself is not capable of catching.
IComparable and IComparable<T>
I will stop being too critical. I don't have problem with those.
Ok, I can't stop it. Not having problems with them does not mean I consider them completely correct. They still add responsibilities to the classes and specially for the string
type it is a big responsibility, as localization can affect how it works.
But as it is extremely normal to change comparison rules I only see IComparable
being used as the default case, not as the only case, so the big problem caused by the other interfaces is not really present to these ones.
IDrawable, IPrintable, IRemotable or any interface that simple add methods but don't change the state of the object
Even if those are ficticious interfaces, any interface that simple adds methods to an object to make it useable in another environment can take advantage of an actionless framework.
Instead of forcing the implementors to think about all the places where the object could be used, let them do a lazy job and, if another action should be done with the object, allow that to be created later.
Interfaces that add properties or change states of an object
Even if in some cases it is still possible to create some "mix-ins" I will say it will not be the job of an actionless framework to do so.
In those cases we can see interfaces like IDisposable
(which frees objects resources immediately and usually makes it unusable), IList
(which can change the contents of the list) and many others.
Those to me are good interfaces. We are giving abstracted ways to access the same object and many of them are usually related to the primary action of the type. For example, even if the IEnumerable
interface does not change the contents of a collection, I can't think about someone creating a collection type and simple forgetting to implement that interface. The first time he tests his code with a foreach
he will notice the problem.
Adapter Actionless Framework
I already presented an article about creating adapters at run-time, named DelegatedTypeBuilder - Creating New Types and Adapters at Run-Time. The idea was simple to get an instance of any type and ask to receive it as an specific interface type.
That implementation used Reflection.Emit
to create a class that implemented such interface and redirected all methods to methods with the same name on the real instance, trying to do any casts or conversions if needed (again, conversions...).
That was too specific too, and I think it can be one of the alternatives used by an adapter framework.
But, guess what, the conversion framework is also a good place to create adapters. If you want a type X
that is compatible with interface I, but does not implement it, what will happen if you try to convert it? With the right converter, it can end-up "adapting" the type.
Factories
Maybe you think that everything I said is simple a factory.
Well, actionless frameworks can be great factories, but factories are not required to be actionless frameworks and actionless frameworks are not constrained to creating instances as factories are.
Factories don't need to be run-time configurable, don't need to have local/global interaction and are not required to have a last-chance event. All of those can be added to a factory and, in this case, it will become an actionless framework too. But it is much easier to add such traits during an initial phase than adding them later and correcting all work-arounds that may be found everywhere else.
Want to see them in action?
At this moment I am still trying to finish all those frameworks and I am also correcting my old applications to use the new frameworks instead of the old ones. This also let me test the frameworks and see if they still have implementation bugs or even some conceptual limitations.
I don't know when, but I will try to put the code for those frameworks here and also some examples on how to use them. For the moment I can only say that the Converters article has an almost complete implementation on the matter.
Conclusion
I am getting to the conclusion that I am too critical. I love .Net and I know many of its resources were and still are revolutionary and, even then, I criticize it. I hope I have valid points, though.
Version History