Click here to Skip to main content
15,867,756 members
Articles / Programming Languages / C# 4.0

Actionless Frameworks

Rate me:
Please Sign up or sign in to vote.
4.80/5 (25 votes)
14 Jan 2015CPOL23 min read 41.7K   44   23
Understand how to create fully configurable frameworks that let your application evolve without creating bottlenecks.

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 lookless 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 lookless control means a control that has no mandatory face. It usually has a default face but the control is still considered lookless 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 simply 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 simply 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

Simply put, actionless frameworks are extremely prepared for future changes and can make your application behave better/look better by a new configuration.

If you are using it to edit records, then if in the future you create a better editor, you can simply 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, simply 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 where 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 simply 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 simply 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 simply 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 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.

Local-Global interaction

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?

  1. Search actions registered locally;
  2. Search actions registered in the parent;
  3. Search actions registered globally;
  4. Execute local Searching;
  5. Execute parent Searching;
  6. Execute GlobalSearching.

Or should we execute in this order?

  1. Search actions registered locally;
  2. Execute local Searching;
  3. Search actions registered in the parent;
  4. Execute parent Searching;
  5. Search actions registered globally;
  6. 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:

  1. Search locally;
  2. Search the parent;
  3. Search globally;
  4. 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 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 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 simply tell where is the default serializer class by an attribute or in the static constructor?

Also, by 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 EquatableListss, 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 simply add methods but don't change the state of the object

Even if those are ficticious interfaces, any interface that simply 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 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 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 simply 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionInformation Pin
Member 1279353914-Oct-16 2:11
Member 1279353914-Oct-16 2:11 
AnswerRe: Information Pin
Paulo Zemek14-Oct-16 16:04
mvaPaulo Zemek14-Oct-16 16:04 
QuestionVisual Modeling will help understand the architecture Pin
Reader Man San21-Aug-15 3:47
professionalReader Man San21-Aug-15 3:47 
AnswerRe: Visual Modeling will help understand the architecture Pin
Paulo Zemek21-Aug-15 6:46
mvaPaulo Zemek21-Aug-15 6:46 
GeneralRe: Visual Modeling will help understand the architecture Pin
Reader Man San21-Aug-15 11:25
professionalReader Man San21-Aug-15 11:25 
QuestionAnother good one! Pin
Sander Rossel7-Feb-15 12:03
professionalSander Rossel7-Feb-15 12:03 
AnswerRe: Another good one! Pin
Paulo Zemek7-Feb-15 12:07
mvaPaulo Zemek7-Feb-15 12:07 
GeneralGood thoughts but premature Pin
Chris Jacobi15-Jan-15 13:12
Chris Jacobi15-Jan-15 13:12 
QuestionGreat ideas, one word of caution... Pin
J Healy14-Jan-15 21:18
J Healy14-Jan-15 21:18 
AnswerRe: Great ideas, one word of caution... Pin
Paulo Zemek14-Jan-15 21:24
mvaPaulo Zemek14-Jan-15 21:24 
QuestionVery interesting. Pin
Pete O'Hanlon14-Jan-15 12:07
subeditorPete O'Hanlon14-Jan-15 12:07 
AnswerRe: Very interesting. Pin
Paulo Zemek14-Jan-15 12:23
mvaPaulo Zemek14-Jan-15 12:23 
QuestionSearching event Pin
Luka14-Oct-14 0:01
Luka14-Oct-14 0:01 
QuestionActual Implementation? Pin
Member 1019550511-Nov-13 7:20
professionalMember 1019550511-Nov-13 7:20 
AnswerRe: Actual Implementation? Pin
Paulo Zemek11-Nov-13 7:52
mvaPaulo Zemek11-Nov-13 7:52 
QuestionAn interesting idea Pin
Marc Clifton28-Sep-12 3:38
mvaMarc Clifton28-Sep-12 3:38 
AnswerRe: An interesting idea Pin
Paulo Zemek28-Sep-12 9:09
mvaPaulo Zemek28-Sep-12 9:09 
GeneralRe: An interesting idea Pin
Marc Clifton29-Sep-12 7:29
mvaMarc Clifton29-Sep-12 7:29 
GeneralRe: An interesting idea Pin
Garth J Lancaster13-Oct-14 23:48
professionalGarth J Lancaster13-Oct-14 23:48 
GeneralMy vote of 5 Pin
VallarasuS27-Sep-12 22:26
VallarasuS27-Sep-12 22:26 
GeneralRe: My vote of 5 Pin
Paulo Zemek28-Sep-12 1:20
mvaPaulo Zemek28-Sep-12 1:20 
GeneralMy vote of 5 Pin
Isabel Furini27-Sep-12 14:09
Isabel Furini27-Sep-12 14:09 
GeneralRe: My vote of 5 Pin
Paulo Zemek27-Sep-12 14:37
mvaPaulo Zemek27-Sep-12 14:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.