Click here to Skip to main content
Click here to Skip to main content
Go to top

Converters class - Register all your data-type conversions in a single place

, 20 Sep 2012
Rate this:
Please Sign up or sign in to vote.
This article explains how to create a class that can handle any kind of data-type conversion by allowing users to register their own conversions and how to make it work both as a global and also as a local solution, so different threads can do different conversions for the same data-types.

Background

Did you ever use the Convert class? Methods like Convert.ToInt32 or the more generic Convert.ChangeType?

Did you have any problems with it?

Well, I must say that I had a problem with it the very first time I used it (I was receiving a Nullable<int> and the Convert.ChangeType was not prepared for it). The solution? Well, I made many different solutions and I finally decided to write what I consider the definitive solution and that's why I am writing this article.

The Pseudo-Solutions

In my initial case I was doing conversions using the FieldType of a FieldInfo instance and using the Convert.ChangeType method. When I made a field of type int? (or Nullable<int> if you prefer), I got an exception.

At that very first moment, I changed the caller to check if the type was any kind of Nullable, and so it got the generic argument as the real type to use with the ChangeType call.

It is not hard to think that I soon replaced any call of the Convert.ChangeType by a call to something like MyConvert.ChangeType, which did the nullable checking before calling the Convert class.

But the problem didn't end there. Did you already try to convert an int to a Color or vice-versa?

By a direct call to the Color class, that's easy. A color can return its Argb value as an int and can be created by an int, but the Convert class does not know that and putting such knowledge in the MyConvert class was not a great idea either. Will I put every possible conversion there?

Pseudo-solution 1

My first generic solution was to use a Dictionary where the key was a pair of Input/Output type and the value was a delegate to do the conversion.

That worked great, but during the application startup I was forced to register all the possible conversions.

Maybe you ask: How is this different from putting all the conversions in the MyConvert class directly?

Well, the difference is that the MyConvert class could be created in a generic assembly, can be used by that generic assembly and all the needed conversions could be added by the application, not by the library, using types that were not known when the library was compiled.

Pseudo-solution 2

With the first solution I needed to register all the conversions at the application startup, even the generic ones.

So, if I wanted to convert an IEnumerable<int> to an int[] I needed to register a converter. If I wanted to convert an IEnumerable<string> to a string[] I needed another converter.

Considering that such things were becoming common, I simple added some built-in conversions for the generic cases. So, if I discovered it was a conversion from any kind of enumerable to an array, the code was built-in in the MyConvert class.

So, yes, it was a mix. Some generic conversions were done directly in the MyConvert class while the others were registered at the application level.

Pseudo-solution 3

I think you can already imagine. I put a conversion from enumerables to arrays. But, in another application, I wanted the conversion to lists instead of arrays and I ended up changing the library.

I wanted to convert any enum to an int, and I also put that generic knowledge in the library. Even if many applications could benefit from those changes, it was not good to keep changing the library (or more specifically, the class) and I did one of the simplest things I could: I put an event and removed the code that did the generic conversions from the MyConvert class. So, if a direct converter was not found, the event was invoked and there the generic conversions could be done. Easy, isn't it?

With this third solution I simplified the class. It was working accordingly to the Single Responsibility Principle, direct conversions and generic conversions could be added and there was no need to keep changing the class for different kinds of conversion.

So, this could be the ultimate solution, but not to me.

Reasons why I was not satisfied yet

The pseudo-solution 3 was fully functional in the sense that any kind of conversion could be added to it, be it a generic conversion or a specific conversion, without requiring the class to change.

But there were some weak areas. The first one was thread-safety.

The class was not thread-safe. Surely that was not be a problem when all the conversions were registered during the application startup. But when writing common libraries I always expect for the worse. What will happen if one thread is looking for a converter while another thread is registering a new unrelated one?

Considering the class was static, it should be thread-safe.

Easy, use a lock.

That was my initial thinking but when using the class to do millions of conversions putting a lock was a performance killer. Should I really make the class thread-safe? Should I simple put a warning that the class is not thread-safe while registering new converters?

It was not an easy decision. But then, I had a new problem.

Two threads wanted different conversions for the same types. In fact, one thread wanted to convert bools to chars as 0 and 1 while another thread wanted the conversion to be F and T.

Maybe you think that it was a very particular case and I should simple threat it differently, but the code doing the conversion was part of the ORM and, so, it couldn't be done differently for that particular case.

Any work arounds?

Sure. The bool converter could use a thread-static variable to know how the conversion should be done. So, the thread that needed the different conversion should set a different value in the thread-static variable.

It worked, but it was not yet great to me.

Thinking differently

For a different kind of problem I really needed a lock, as different values were constantly added by different threads, but added values were never changed.

So, to gain performance and avoid excessive lockings, each thread made a copy of the already existing values. When a new value was added, it was added locally (without a lock) and to the global object with a lock. When a value was not found locally, a lock was made to search it in the global object.

But for the most part the same values were returned from the local copy, without a lock.

Can you see how this inspired my last solution?

Finally, the actual solution

The actual solution is based on the principle of global/local configuration.

The global configuration is composed of the static methods and events. Anything put there is valid for the entire application and uses locks.

But, when requesting for a converter, you should request it on a thread-specific instance. So, the non-static methods are not thread-safe, but you gain access to them by requesting a thread-specific instance.

When searching for a converter, the local dictionary is checked. If there is no conversion there, the local Searching event is invoked. If in the searching event a conversion is returned, it is registered (locally only) so the next time it will not call the event again.

If not, then the global (static) configuration is used. In fact, it is the same pattern, looking for a configuration already registered and, if none is found, invoking the GlobalSearching event, but everything inside a lock. If a converter is found, it is again registered locally, so the next time there is no need to search for it on the global configuration and the lock is avoided.

OK, there is another small detail. In the other solutions, when a conversion was not found the event was invoked to do the conversion. The new solution invokes the event to try to get a converter.

The advantage of the new method is that you can get a Converter delegate to any supported conversion (be it registered directly or through the Searching event) and then you can execute it as many times as needed, without a new lookup in the dictionary and without extra calls to the Converting events. So, when well used, the performance is even better than the pseudo-solutions.

So, let's check the Strong Points:

     
  • Conversions can be done for third-party types that do not support those conversions by default. A simple example is the conversion between Color and int, which are not supported by the Convert class;
  •  
  • At the same time it allows you to add conversions for third-party types it respects the Single Responsibility Principle. After all, a type should not care on how to convert itself in every possible way. And a class that has all the conversions of the application is also not a good idea. But a class that allows to register all the conversions, that's OK;
  •  
  • Each registered Converter is a typed delegate. So, considering the implementation of the converters themselves don't do boxing, such performance hit can be avoided;
  •  
  • If you need to do the same conversion thousands of times you can get the delegate only once, then re-execute it thousands of times. This is faster than, for example, calling Convert.ChangeType thousands of times with the same target type;
  •  
  • Configuration can be really global, thread-local or even "context" local. So you can have a completely different configuration for a specific call without affecting the rest of the application and without requiring the called code to know about the change on the conversions.

Weak points of this approach:

     
  • The Converters class does not start with any supported conversion. Surely with the sample you can see how to register the most basic ones, but it is always the job of the application to register the right conversions and Searching delegates;
  •  
  • Returning a typed-delegate from the Searching event can be tricky. I hope the classes in the GenericConverters folder help understand how to do it but, still, it's a kind of disadvantage;
  •  
  • Some users may consider it odd that we need to get a local instance to do the conversions instead of simple doing the conversions from static methods;
  •  
  • It's not a disadvantage of the approach but this code is new and, even if I tried to test it, maybe there are still some bugs. So, if you find one, please let me know and I will try to correct it as soon as possible.

In which applications can I use the Converters class?

In any application that needs a generic data-type conversion. If you use the Convert class but also have some extra tests to see if the Type is nullable or not, you will probably love this class. If you have a WPF application that always convert the same types in the same way, you can use this. For WPF and Silverlight I would really love to set this class as the default converter but as I don't know how to do that (I really think it is impossible to set a default converter), I made the ConvertExtension and the SmartBinding classes. With them, you can do a binding like this:

{Binding Converter={App:Convert}, Path=SomePath}
or
{App:SmartBinding SomePath}

and the appropriate registered conversion will be used. See more info at the end of the article.

Is this a copy?

There are two articles that also deal with an "ultimate" (or as they called, Universal) converter. They are:

So, is this a copy?

The answer is no. I already did this kind of converters many times, generally as part of an ORM framework. But such articles really inspired me to make it a completely isolated component and the former one was also the inspiration to create the FromTypeDescriptorConverter class.

I consider that even if we focused on the same problem, the approaches are very different. Choose the solution you feel more comfortable with.

Changes

I just published this article and, after reviewing it, I noticed that I didn't add a class to make the conversions from string to nullable types. When testing, I noticed that the TypeDescriptors where already doing that but I still wanted to give that code and, while doing it, I made some major revisions to the class.

So, what changed? Well, in the article, from this point it is everything new and the Using the Code can be a very interesting point for future users. But in the implementation, there were the following changes:

  • The Searching and GlobalSearching events are no more declared as EventHandler<SearchingConverterEventArgs>. They are both declared as SearchingConverterEventHandler, with in turn only receives the args parameter, without the sender. I noticed that in most implementations the sender was completely unused. Also, it was not typed, forcing a cast if it was needed. Now, the sender, named Converters and rightly typed, is a property of the SearchingConverterEventArgs;
  • The SearchingConverterEventArgs also gained the IsGlobal property that can be used to identify if the event handler is being called by a Searching or by a GlobalSearching call;
  • Even when doing a GlobalSearching the local Converters instance is put into the event args. Also, if for some reason a GlobalSearching event returns a Converter that used local information (like being based on another local handler) it is possible to call the MakeLocal() method on the args so such Converter will not be stored globally, only locally;
  • Renamed the UntypedGet to CastedGet.

A Global Convert Method

It may be a personal opinion but I don't like such global method. My reason is that it can easily become a source of bad performance.

Look at these two samples:

while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = GlobalConvert.ChangeType<Color>(reader.ReadInt32(0));
  result.EndColor = GlobalConvert.ChangeType<Color>(reader.ReadInt32(1));
  yield return result;
}

and

while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = Converters.LocalInstance.GetConverter<int, Color>()(reader.ReadInt32(0));
  result.EndColor = Converters.LocalInstance.GetConverter<int, Color>()(reader.ReadInt32(1));
  yield return result;
}

Looking at the first piece of code, without knowing about the existence of the second piece, I will say that it is completely right. There is no repetitive code and there is nothing that I can pre-store to make things faster.

On the other hand, if I look at the second piece of code without knowing the first piece of code I immediatelly see that something is wrong. Why am I getting the local instance and the converter again and again? Wouldn't it be better if I simple got the converter once, outside the loop, and then used it all the time? Something like this:

var intToColorConverter = Converters.LocalInstance.GetConverter<int, Color>();
while(reader.Read())
{
  var result = new Gradient();
  result.StartColor = intToColorConverter(reader.ReadInt32(0));
  result.EndColor = intToColorConverter(reader.ReadInt32(1));
  yield return result;
}

To me that's an instintive thinking. If you are repeating the same "get" inside a loop, which is going to return the same value all the time, put it outside.

And that's why I don't like the fact that a global static method can do the conversion directly. It is as bad as the second example, but it doesn't seem to be wrong (which is even worse). So, if you don't know the specific details you will see the code, will say that's it's OK and, if there is a performance problem, you will probably find work-arounds. So, can I put the GlobalConvert in my library? Yes, I can, but I will not do it.

But, if you really consider it a good addition and are sure you will only use it when it is a non-repetitive convert, then you can create such class with the following code:

using System;

namespace ConfigurableConvertTest
{
  public static class GlobalConvert
  {
    public static object ChangeType(object value, Type destinationType)
    {
      if (destinationType == null)
        throw new ArgumentNullException("destinationType");

      if (value == null)
        return null;

      var converter = Converters.LocalInstance.CastedGet(value.GetType(), destinationType);
      var result = converter(value);
      return result;
    }

    public static T ChangeType<T>(object value)
    {
      if (value == null)
        return default(T);

      object result = ChangeType(value, typeof(T));
      return (T)result;
    }
  }
}

And I already added it to the download code. But, remember, if you use it, be sure to don't use it in a loop if you can get the converter before entering the loop.

Using the Code

To use the code you must remember that it is your responsibility to initialize the Converters class. Such class can deal with all possible conversions, but comes initialized with none.

But let's start with the usage. To do conversions, you should get a local (thread-specific) instance of the converters class, then you ask for all the converters you want. You store them and only then you used the stored converters to do all your needed conversions. So, a good pattern is this:

var localConverters = Converters.LocalInstance;
var intToString = localConverters.Get<int, string>();
var stringToInt = localConverters.Get<string, int>();
while(someCondition)
{
  int intVariable = stringToInt(someString);
  string stringVariable = intToString(someIntValue);
}

If the destination type is got by a run-time Type, instead of at compile time, you can get a converter that receives and returns the value as object. To do so, call the CastedGet method, like this:

var converter = localConverters.CastedGet(inputType, outputType);

Of course, the localConverters must already be stored (like on the previous example) and the inputType and outputType are the source and destination types. The difference is that, as long you have values compatible with the input type, even if you are receiving them cast as object, the returned converter will work.

But to continue on how to use the code, at least once you will need to register the converters. So, let's see:

Registering a global converter

To add a global conversion (that is, a conversion that will be valid for all threads), you can do it like this:

Converters.GlobalRegister<string, int>((value) => int.Parse(value));

In this case, I am registering a string to int conversion, and the code to do the conversion is the anomymous delegate that simple does an int.Parse. Easy, isn't?

Registering a local converter

But if you want to register only a local conversion (that is, valid only for the actual thread), you should initially get the LocalInstance. I really recomment getting the LocalInstance first and storing it into a variable, so if you need to add many conversions you only lose time getting the LocalInstance once. So:

var localConverters = Converters.LocalInstance;
localConverters.Register<string, int>((value) => int.Parse(value));
//localConverters.Register... some other conversion here.

The Generic Conversions

To register a generic conversion that already exists, it is enough to add the SearchingConverterHandler to the GlobalSearching or to the Searching event. The Searching event also needs the local instance, so, considering you have already stored it, adding the handler to the local thread only will be adding this extra line:

localConverters.Searching += StringToNullableConverter.SearchingConverterHandler;

The StringToNullableConverter is capable of dealing with conversions from string to any kind of nullable, if the conversion from string to the value-type already exists. That is, if there is a conversion from string to int, it is capable of adding the conversion from string to a nullable int. If there is a conversion from string to double, it will be capable of registering a conversion from string to nullable double. I think that's enough to understand it.

Creating your own generic conversion logic

You know there are some already existing generic converters and you want to create your own, but at this moment you have no clue on how to do it.

The best I can do is to try to explain one of the already existing generic converters, so, let's see one:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConfigurableConvertTest.GenericConverters
{
  public static class ToArrayConverter
  {
    public static Delegate Get(Type dataType)
    {
      if (dataType == null)
        throw new ArgumentNullException("dataType");

      var toArrayConverterType = typeof(ToArrayConverter<>).MakeGenericType(dataType);
      var instanceProperty = toArrayConverterType.GetProperty("Instance");
      var converter = instanceProperty.GetValue(null, null);
      return (Delegate)converter;
    }

    private static readonly SearchingConverterEventHandler _searchingConverterHandler = _SearchingConverter;
    public static SearchingConverterEventHandler SearchingConverterHandler
    {
      get
      {
        return _searchingConverterHandler;
      }
    }
    private static void _SearchingConverter(SearchingConverterEventArgs args)
    {
      var outputType = args.OutputType;
      if (!outputType.IsArray)
        return;

      var elementType = outputType.GetElementType();
      var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);

      var inputType = args.InputType;
      if (enumerableType.IsAssignableFrom(inputType))
        args.Converter = Get(elementType);
    }
  }

  public static class ToArrayConverter<T>
  {
    private static readonly Converter<IEnumerable<T>, T[]> _instance = _Convert;

    public static Converter<IEnumerable<T>, T[]> Instance
    {
      get
      {
        return _instance;
      }
    }

    private static T[] _Convert(IEnumerable<T> enumerable)
    {
      if (enumerable == null)
        return null;

      var result = enumerable.ToArray();
      return result;
    }
  }
}

This is the full code of the ToArrayConverter without the documentation comments. It is divided in two classes. The generic one (the second) is where the conversion is really done. As you can see, the _Convert method simple checks if the enumerable is null to return null or calls the ToArray() method over that enumerable.

Such method already conforms to the Converter delegate, and I store such delegate in the _instance variable. But the hard part is: How can I get such typed delegate from an untyped event handler? And how can I detect that it is the valid handler to get?

That's why there is the non-generic class. It uses reflection to get the generic ToArrayConverter thanks to a data-type (the type used to fill the T) and to get the value of the instance property.

And the code used to fill the Searching or GlobalSearching event checks if the expected output type is an array, then checks if the input-type is an enumerable with the same element-type as the array. If it is, then it uses the Get method to get such converter. What can I say? Generic programming + reflection is not really that easy.

Local Configurations

The Converters class has a global and a local configuration, but the local configuration is thread-based, so you can think it will not work, because:

  • You are using the ThreadPool and don't know what else can already be configured on that thread;
  • You are using Tasks, not Threads;
  • You want to change a certain conversion only for the actual method but then you want everything to work as before, without keeping the change for the entire lifetime of the actual thread;
  • Maybe you have something else in mind.

I though about those problems and I changed a little how the Converters search really work. Do you remember that I said that the Converters searches locally and, if it does not find a converter it uses the global configuration?

Well when it does not find a converter it checks the Mode of the actual Converters instance. The mode can be:

  • BasedOnPreviousLocal: In this mode, a local configuration asks for the previous local configuration to do the search. So, as you may imagine, you can create and replace the active Converters instance.
  • BasedOnGlobal: Independent of previous local configurations (if any), it will ask the global configuration for the converter.
  • Isolated: As the name says, it is isolated. So, if a Converter was not found locally, even if there is a valid global one, it will not be used.

Setting the Mode

To set the mode you should create a new Converters instance giving it a mode. It will immediately become the active Converters instance and, when disposed, it will recover the previous one. I really recommend creating new Converters instances with a using clause, like this:

using(var localConverters = new Converters(ConvertersMode.Isolated))
{
  // register the isolated conversions here...
  // call any methods that may use the converters class, in which case it will only 
  // see the converters registered in this block.
  // surely you can use a different Mode, but for this example, it is Isolated.
}
// here the old configuration takes place again.

With this simple approach you make the configuration valid for "scopes". Surely the code is still bound to a thread, but that's a technical detail. The code will not care if it is running in a ThreadPool thread, in a Task or how your method was called. You tell when the new Converters take place and when it goes away. So you can create really localized configurations without needing to pass that configuration to inner methods.

Delegate Types - A Dilemma

You may have noticed that I used the Converter<TInput, TOutput> delegate. Before actually starting to write the Converters class I though about using the Func<T, TResult> delegate. But, while looking to see if a generic converter already existed, I found the Converter<TInput, TOutput> delegate.

That made me think that there was some kind of generic converter but while looking at the documentation I saw it was used by lists and arrays to do the conversion of all their items, without any use outside that context.

The Func<T, TResult> is much more known but, still, a delegate named Converter looks much more suitable for doing conversions than a delegate named Func. Also, TInput and TOutput looks clearer than T and TResult. So, I decided to use the Converter delegate.

Was it a good choice?

I am still not sure. I had old code that did conversions and used the Func delegate. But, as I know where those classes are and where they were used, I simple changed those to use the Converter delegate too. But, if that was not the case, can I use a Func to fill a Converter, or vice-versa?

The answer is no. Even if the same method can be used when instantiating a new Func or Converter we can't simple use one in the place of the other. One possible solution can be this:

public Func<TInput, TOutput> ChangeConvertToFunc<TInput, TOutput>(Convert<TInput, TOutput> convert)
{
  return (input) => convert(input);
}

Effectivement, we will be creating a new delegate that calls the previous one. That's good, right?

Well, no. It works and for something small it can be OK. But it is adding a new virtual call. What will happen if we convert from Func to Convert and from Convert to Func 100 times? We will end-up with 200 levels of indirection.

So, is there a better solution?

Yes. I don't know why the language does not do this automatically, but we can create a delegate using the Delegate.CreateDelegate method by using the source's delegate Method and Target, as the method is already compatible with the output delegate.

We could implement such conversion like this:

public static TResultDelegate ConvertDelegate<TResultDelegate>(Delegate input)
{
  if (input == null)
    return null;

  var target = input.Target;
  var method = input.Method;
  object result = Delegate.CreateDelegate(typeof(TResultDelegate), target, method);
  return (TResultDelegate)result;
}

This method is capable of converting any input delegate to a compatible output delegate. So, it will be capable of converting a Convert delegate to a Func delegate and vice-versa.

But, to make my Converters class complete, I created the DelegateConverter class, that has a handler to fill the conversion from one delegate type to another.

That is, you can use the Converters class to convert an int to an string, a Color to a Brush, an enum to its underlying type or even one delegate type to another.

This still doesn't solve my dilemma if a should use the Convert or the Func delegate, but at least if I end-up arriving in a situation where I need to change from one type to another, I could use the same Converters class to do the job.

ConvertExtension and SmartBinding

In the WPF sample you will notice the utilisation of {App:Convert} and {App:SmartBinding} in the XAML.

Those two classes end-up setting the Converter of the binding to a ConvertersConvert instance.

Why two classes? I personally prefer the SmartBinding but, who knows, maybe you already have a Binding sub-class and you only want to use the generic converter, so, there is an alternative.

Can I put the ConvertersConvert as a resource and reuse it for all my bindings?

No. The ConvertersConvert stores the delegate used to do the conversion at the first conversion and then it reutilises that same delegate all the time, to have a good performance. So, if all the Bindings have the same input and output types, it will work. If not, it will throw exceptions. If you are unsure, don't try to reutilise.

The Samples

There are two samples. One is a console application and it is mostly useful if you DEBUG it. The only think it will really show is a speed comparison when converting an enum to int millions of times, but it also does many other conversion with the sole purpose of testing.

There is also a WPF application that uses the ConverterExtension and the SmartBinding classes.

If you think it's strange that I am using the Console Application as a library for the WPF application, well, I consider it too. But I am really considering that users will copy the classes they want into their own applications instead of picking up the console application as a library.

This time I am doing the opposite of what I usually do and there is no download of my personal libraries. But guess what, this generic conversion class is going to become part of my personal library.

History

  • 20 Sep 2012: Added support to convert delegate types and explained the SmartBinding class;
  • 19 Sep 2012: Added the ConvertersMode and the Local Configurations topic;
  • 18 Sep 2012: Added the StringToNullableConverter and added the Using the Code topic;
  • 17 Sep 2012: First version.

License

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

Share

About the Author

Paulo Zemek
Architect
Canada Canada
I started to program computers when I was 11 years old, as a hobbist, 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 they 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
Microsoft MVP 2013

Comments and Discussions

 
GeneralMy vote of 5 PinmemberNicolas Dorier2-Nov-12 16:48 
GeneralRe: My vote of 5 PinmvpPaulo Zemek3-Nov-12 2:51 
SuggestionReplacing the converter (untested) PinmemberFrederico Barbosa30-Oct-12 1:22 
GeneralRe: Replacing the converter (untested) PinmvpPaulo Zemek30-Oct-12 2:57 
GeneralRe: Replacing the converter (untested) PinmemberFrederico Barbosa30-Oct-12 3:34 
GeneralRe: Replacing the converter (untested) PinmvpPaulo Zemek30-Oct-12 4:39 
GeneralRe: Replacing the converter (untested) PinmemberFrederico Barbosa30-Oct-12 5:55 
GeneralMy vote of 5 PinmvpKanasz Robert20-Sep-12 1:38 
GeneralRe: My vote of 5 PinmvpPaulo Zemek20-Sep-12 2:50 
QuestionMy vote of 5 PinmemberThorsten Bruning17-Sep-12 20:57 
AnswerRe: My vote of 5 PinmvpPaulo Zemek18-Sep-12 1:23 
GeneralRe: My vote of 5 PinmemberThorsten Bruning18-Sep-12 9:37 
GeneralRe: My vote of 5 PinmvpPaulo Zemek18-Sep-12 10:31 
GeneralRe: My vote of 5 PinmemberThorsten Bruning20-Sep-12 5:31 
By the way - AMOS Basic on the Amiga was really cool Wink | ;)
10 Home
20 Sweet
30 goto 10

QuestionHow about performance? PinmemberThanasis Ioannidis17-Sep-12 11:28 
AnswerRe: How about performance? PinmvpPaulo Zemek17-Sep-12 11:34 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 20 Sep 2012
Article Copyright 2012 by Paulo Zemek
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid