Wednesday, June 18, 2008

Object Deep Cloning using IL in C# - version 1.0

Howdy,

If you've read the rest of my blog, then you'll have noticed that I have another post that addresses cloning of objects using IL (Intermediate Language)
If you haven't, then you can find the post 'Object cloning using IL in C#'.

The reason I published this post is because the code in my previous post (mentioned above) makes a shallow copy of an object. It copies only references of classes, value-types are off course copied by value. So if you have a class with only value types or strings (immutable), then the shallow copy will act as a deep copy (sort of).

Now, imagine that you have (like in most projects) objects that contain other objects, like a person object that has one legal address and different optional addresses. Then you'll probably create a Person class and an Address class,
in the person class you'll make a property which has the type 'Address' and a property that's a list of Addresses.

If you would take a shallow clone, and you change an address in the clone, then it will also be changed in the original person.address object.

So, i've created a solution that clones the 'Person' object in this case, with the addresses, but it makes new instances of each address.

The only constraints however are that the 'to-clone' objects MUST have a default constructor for instantiating the class, if the system can make an instance, then the private fields will automatically be copied.

Like I mentioned in my previous post, you'll only loose performance the first time, because of all the lookups and the generating of the IL-code.
After that, the compiled code will be executed.
I tried to optimize the IL code as much as possible; maybe it can be optimized even more.

For example:

If you would clone the address of a person in normal C# code then you can do:

public Person Clone(Person p) 

    Person clone 
= new Person()
    
clone.Address = new Address()
    
// normally you should check on null, 
    // but let's assume that it's never null. 
    
clone.Address.ID p.Address.ID;
    return 
clone;
}

or you can do:

public Person Clone(Person p) 

    Person clone 
= new Person();
    
Address a = new Address();
    
clone.Address a;
    
// normally you should check on null, 
    // but let's assume that it's never null. 
    
a.ID p.Address.ID;
    return 
clone;
}

The performance of the second option is 'higher'.

You could ask, why?
Well, if you would disassemble these two code samples, then you'll notice that the second code needs fewer instructions.

First sample decompiled:

.locals init (
     [0] class Cloning.Person clone)
L_0000: nop
L_0001: newobj instance void Cloning.Person::.ctor()
L_0006: stloc.0
L_0007: ldloc.0
L_0008: newobj instance void Cloning.Address::.ctor()
L_000d: callvirt instance void Cloning.Person::set_Address(class Cloning.Address)
L_0012: nop
L_0013: ldloc.0
L_0014: callvirt instance class Cloning.Address Cloning.Person::get_Address()
L_0019: ldarg.1
L_001a: callvirt instance class Cloning.Address Cloning.Person::get_Address()
L_001f: callvirt instance int32 Cloning.Address::get_AddressID()
L_0024: callvirt instance void Cloning.Address::set_AddressID(int32)

L_0029: nop
L_002a: ldloc.0
L_002b: ret


Second sample decompiled:

.locals init (
     [0] class Cloning.Person clone,
     [1] class Cloning.Address a)
L_0000: nop
L_0001: newobj instance void Cloning.Person::.ctor()
L_0006: stloc.0
L_0007: newobj instance void Cloning.Address::.ctor()
L_000c: stloc.1
L_000d: ldloc.0
L_000e: ldloc.1
L_000f: callvirt instance void Cloning.Person::set_Address(class Cloning.Address)
L_0014: nop
L_0015: ldloc.1
L_0016: ldarg.1
L_0017: callvirt instance class Cloning.Address Cloning.Person::get_Address()
L_001c: callvirt instance int32 Cloning.Address::get_AddressID()
L_0021: callvirt instance void Cloning.Address::set_AddressID(int32)
L_0026: nop
L_0027: ldloc.0
L_0028: ret


As you see in the code of the first sample, you need 5 callvirt's, and it's more IL code,
in the second sample you have 4 callvirt's and less code, because we have a kind of shortcut to the address object
from the local store [1].

When you create an object, you push it onto the stack and then you store it in a local store (local variable).
If you use a.ID then you need to lookup the ID from the local store reference,
else if you use clone.Address.ID then you need to lookup the Address reference from the person object (also in a store), and then lookup the ID reference from the retrieved Address reference, and then you can do an action with it.
(lookat line L_0013 to L_0024 of first sample and line L_0015 to L_0021 in second sample)

In that manner I tried to optimize all the calls in the IL code, so that I can do the required job in as less instructions as possible.

Here is the result:

Now let's take a look at the code:

Our revisited Person class with an Address class:


using System;
using 
System.Collections.Generic;
using 
System.Text;

namespace 
Cloning
{
    
public class Person
    {
        
private int _id;
        private string 
_name;
        private string 
_firstName;
        private string 
_field1, _field2, _field3;

        public 
Person()
        {
            
this.Addresses = new List<Address>();
        
}

        
public int ID
        {
            
get return _id}
            
set { _id = value; }
        }

        
public string Name
        {
            
get return _name}
            
set { _name = value; }
        }

        
public string FirstName
        {
            
get return _firstName}
            
set { _firstName = value; }
        }

        
private Address _address;

        public 
Address Address
        {
            
get return _address}
            
set { _address = value; }
        }

        
private List<Address> _addresses;

        public 
List<Address> Addresses
        {
            
get return _addresses}
            
set { _addresses = value; }
        }

    }

    
public class Address
    {
        
public Address()
        {
            
this.AddressID -1;
        
}

        
public Address(int aid)
        {
            
this.AddressID aid;
        
}

        
private int _addressID;

        public int 
AddressID
        {
            
get return _addressID}
            
set { _addressID = value; }
        }

        
private string _street;

        public string 
Street
        {
            
get return _street}
            
set { _street = value; }
        }

        
private string _city;

        public string 
City
        {
            
get return _city}
            
set { _city = value; }
        }

    }
}


The Cloning class used for testing:

using System;
using 
System.Collections.Generic;
using 
System.Text;
using 
System.Reflection;
using 
System.Reflection.Emit;
using 
System.Threading;
using 
System.Collections;

namespace 
Cloning
{
    
/// <summary>    
    /// Delegate handler that's used to compile the IL to.    
    /// (This delegate is standard in .net 3.5)    
    /// </summary>    
    /// <typeparam name="T1">Parameter Type</typeparam>    
    /// <typeparam name="TResult">Return Type</typeparam>    
    /// <param name="arg1">Argument</param>    
    /// <returns>Result</returns>    
    
public delegate TResult Func<T1, TResult>(T1 arg1);

    public class 
Cloning
    {
        
/// <summary>    
        /// This dictionary caches the delegates for each 'to-clone' type.    
        /// </summary>    
        
private static Dictionary<Type, Delegate> _cachedIL = new Dictionary<Type, Delegate>();
        private static 
Dictionary<Type, Delegate> _cachedILDeep = new Dictionary<Type, Delegate>();
        private 
LocalBuilder _lbfTemp;

        
/// <summary>    
        /// Clone one person object with reflection    
        /// </summary>    
        /// <param name="p">Person to clone</param>    
        /// <returns>Cloned person</returns>    
        
public static Person CloneObjectWithReflection(Person p)
        {
            FieldInfo[] fis 
p.GetType().GetFields(System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)
;
            
Person newPerson = new Person();
            foreach 
(FieldInfo fi in fis)
            {
                fi.SetValue(newPerson, fi.GetValue(p))
;
            
}
            
return newPerson;
        
}

        
/// <summary>    
        /// Clone a person object by manually typing the copy statements.    
        /// </summary>    
        /// <param name="p">Object to clone</param>    
        /// <returns>Cloned object</returns>    
        
public static Person CloneNormal(Person p)
        {
            Person newPerson 
= new Person();
            
newPerson.ID p.ID;
            
newPerson.Name p.Name;
            
newPerson.FirstName p.FirstName;
            
newPerson.Address = new Address();
            
newPerson.Address.AddressID p.Address.AddressID;
            
newPerson.Address.City p.Address.City;
            
newPerson.Address.Street p.Address.Street;
            if
(newPerson.Addresses!=null)
            {
                newPerson.Addresses 
= new List<Address>();
                foreach 
(Address a in newPerson.Addresses)
                {
                    newPerson.Addresses.Add(a)
;
                
}
            }
            
return newPerson;
        
}

        
/// <summary>    
        /// Generic cloning method that clones an object using IL.    
        /// Only the first call of a certain type will hold back performance.    
        /// After the first call, the compiled IL is executed.    
        /// </summary>    
        /// <typeparam name="T">Type of object to clone</typeparam>    
        /// <param name="myObject">Object to clone</param>    
        /// <returns>Cloned object</returns>    
        
public static T CloneObjectWithILShallow<T>(T myObject)
        {
            Delegate myExec 
= null;
            if 
(!_cachedIL.TryGetValue(typeof(T), out myExec))
            {
                
// Create ILGenerator (both DM declarations work)
                // DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), 
                //      new Type[] { typeof(T) }, true);
                
DynamicMethod dymMethod = new DynamicMethod("DoClone"typeof(T), 
                    
new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);
                
ConstructorInfo cInfo myObject.GetType().GetConstructor(new Type[] { });
                
ILGenerator generator dymMethod.GetILGenerator();
                
LocalBuilder lbf generator.DeclareLocal(typeof(T));
                
generator.Emit(OpCodes.Newobj, cInfo);
                
generator.Emit(OpCodes.Stloc_0);
                foreach 
(FieldInfo field in myObject.GetType().GetFields(
                        System.Reflection.BindingFlags.Instance 
                        | System.Reflection.BindingFlags.NonPublic 
                        | System.Reflection.BindingFlags.Public))
                {
                    generator.Emit(OpCodes.Ldloc_0)
;
                    
generator.Emit(OpCodes.Ldarg_0);
                    
generator.Emit(OpCodes.Ldfld, field);
                    
generator.Emit(OpCodes.Stfld, field);
                
}
                generator.Emit(OpCodes.Ldloc_0)
;
                
generator.Emit(OpCodes.Ret);
                
myExec dymMethod.CreateDelegate(typeof(Func<T, T>));
                
_cachedIL.Add(typeof(T), myExec);
            
}
            
return ((Func<T, T>)myExec)(myObject);
        
}

        
public T CloneObjectWithILDeep<T>(T myObject)
        {
            Delegate myExec 
= null;
            if 
(!_cachedILDeep.TryGetValue(typeof(T), out myExec))
            {
                
// Create ILGenerator (both DM declarations work)
                // DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), 
                //      new Type[] { typeof(T) }, true);
                
DynamicMethod dymMethod = new DynamicMethod("DoClone"typeof(T), 
                    
new Type[] { typeof(T) }, Assembly.GetExecutingAssembly().ManifestModule, true);
                
ConstructorInfo cInfo myObject.GetType().GetConstructor(new Type[] { });
                
ILGenerator generator dymMethod.GetILGenerator();
                
LocalBuilder lbf generator.DeclareLocal(typeof(T));
                
generator.Emit(OpCodes.Newobj, cInfo);
                
generator.Emit(OpCodes.Stloc_0);

                foreach 
(FieldInfo field in typeof(T).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))
                {
                    
if (field.FieldType.IsValueType || field.FieldType == typeof(string))
                        CopyValueType(generator, field)
;
                    else if 
(field.FieldType.IsClass)
                        CopyReferenceType(generator, field)
;
                
}
                generator.Emit(OpCodes.Ldloc_0)
;
                
generator.Emit(OpCodes.Ret);
                
myExec dymMethod.CreateDelegate(typeof(Func<T, T>));
                
_cachedILDeep.Add(typeof(T), myExec);
            
}
            
return ((Func<T, T>)myExec)(myObject);
        
}

        
private void CreateNewTempObject(ILGenerator generator, Type type)
        {
            ConstructorInfo cInfo 
type.GetConstructor(new Type[] { });
            
generator.Emit(OpCodes.Newobj, cInfo);
            
generator.Emit(OpCodes.Stloc, _lbfTemp);
        
}

        
private void CopyValueType(ILGenerator generator, FieldInfo field)
        {
            generator.Emit(OpCodes.Ldloc_0)
;
            
generator.Emit(OpCodes.Ldarg_0);
            
generator.Emit(OpCodes.Ldfld, field);
            
generator.Emit(OpCodes.Stfld, field);
        
}

        
private void CopyValueTypeTemp(ILGenerator generator, FieldInfo fieldParent, FieldInfo fieldDetail)
        {
            generator.Emit(OpCodes.Ldloc_1)
;
            
generator.Emit(OpCodes.Ldarg_0);
            
generator.Emit(OpCodes.Ldfld, fieldParent);
            
generator.Emit(OpCodes.Ldfld, fieldDetail);
            
generator.Emit(OpCodes.Stfld, fieldDetail);
        
}

        
private void PlaceNewTempObjInClone(ILGenerator generator, FieldInfo field)
        {
            
// Get object from custom location and store it in right field of location 0
            
generator.Emit(OpCodes.Ldloc_0);
            
generator.Emit(OpCodes.Ldloc, _lbfTemp);
            
generator.Emit(OpCodes.Stfld, field);
        
}

        
private void CopyReferenceType(ILGenerator generator, FieldInfo field)
        {
            
// We have a reference type.
            
_lbfTemp generator.DeclareLocal(field.FieldType);
            if 
(field.FieldType.GetInterface("IEnumerable") != null)
            {
                
// We have a list type (generic).
                
if (field.FieldType.IsGenericType)
                {
                    
// Get argument of list type
                    
Type argType field.FieldType.GetGenericArguments()[0];
                    
// Check that it has a constructor that accepts another IEnumerable.
                    
Type genericType Type.GetType("System.Collections.Generic.IEnumerable`1[" 
                            
+ argType.FullName + "]");
                    
                    
ConstructorInfo ci field.FieldType.GetConstructor(new Type[] { genericType });
                    if 
(ci != null)
                    {
                        
// It has! (Like the List<> class)
                        
generator.Emit(OpCodes.Ldarg_0);
                        
generator.Emit(OpCodes.Ldfld, field);
                        
generator.Emit(OpCodes.Newobj, ci);
                        
generator.Emit(OpCodes.Stloc, _lbfTemp);
                        
PlaceNewTempObjInClone(generator, field);
                    
}
                }
            }
            
else
            
{
                CreateNewTempObject(generator, field.FieldType)
;
                
PlaceNewTempObjInClone(generator, field);
                foreach 
(FieldInfo fi in field.FieldType.GetFields(System.Reflection.BindingFlags.Instance
                    | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public))
                {
                    
if (fi.FieldType.IsValueType || fi.FieldType == typeof(string))
                        CopyValueTypeTemp(generator, field, fi)
;
                    else if 
(fi.FieldType.IsClass)
                        CopyReferenceType(generator, fi)
;
                
}
            }
        }

    }
}


The Program class with the test code in it.

namespace Cloning
{
    
class Program
    {
        
static void Main(string[] args)
        {
            
// Do some cloning tests...
            
Cloning.TestCloning tc = new Cloning.TestCloning();
            
tc.DoTest();
        
}
    }
}


Explanation:

As you see in the code, the model is a little bit expanded.
But if you shuffle a bit with the code, then you can put it in 1 method again,
I only did this to improve readability, so that you can understand what's happening, and how the code is generated.

I also left out the comments from the IL parts, if you want to know what those statements mean, then look at my other post regarding IL cloning (Object cloning using IL in C#), it's explained there.

Method list of cloning class with a short description:


  • Person CloneObjectWithReflection(Person p)
    This method clones an object using reflection (very slow).

  • Person CloneNormal(Person p)
    This method clones the person class manually.
    I think a lot of people will do/user this method :)


  • T CloneObjectWithILShallow(T myObject)
    Make a shallow copy of an object using IL in a generic method.

  • T CloneObjectWithILDeep(T myObject)
    Make a deep copy of an object using IL in a generic method.

  • void CreateNewTempObject(ILGenerator generator, Type type)
    Generate IL-code to create a new object of a certain type and store it in a local store. (local variable)

  • void CopyValueType(ILGenerator generator, FieldInfo field)
    Generate IL-code to copy the values of a value type to the clone.

  • void CopyValueTypeTemp(ILGenerator generator, FieldInfo fieldParent, FieldInfo fieldDetail)
    Generate IL-code to copy the values of a value type from a store location to the destination address in the clone.

  • void PlaceNewTempObjInClone(ILGenerator generator, FieldInfo field)
    Generate IL-code to reference an object from a store to an address in the clone.

  • void CopyReferenceType(ILGenerator generator, FieldInfo field)
    Generate IL-code to copy the values of a reference type (class) to the clone. Instantiate new objects if needed.


I didn't test LINQ support, i guess that an extra 'exception' has to be added for the IQueryable or something like that, and also Arrays should be added, but that's for in version 2.0 or something like that ;), you can see how the system works and expand it to your needs...

I hope that this post and piece of code is useful for some people, if so, please let me know through a comment, thanks ;)

Regards,
F.

Monday, March 17, 2008

Domain Specific Language (DSL) for Neural Network(s)

Howdy folks,

With this entry I would like to show the possibilities of DSL.
For those who don't know DSL, DSL as in Domain Specific Language,
is a modeling tool for modeling problems in an organization for example.

An example that everybody who's familiar with Visual Studio Standard Edition or higher knows is the Class Designer. It's a graphical tool within VS that allows you to model your classes, and it also generated code in the background.

Seeing this, I did some digging, and I came up on the subject of DSL's or DSL Tools.

As you already know probably, I've got a big interest in A.I., neural networks more specifically. Wouldn't it be cool that you could just draw you neural network structure and let VS generate all the network code for you?
In this way you can make balanced or unbalanced neural networks, which can have a big benefit.

I've made a little design of how the designer should look like.



The boxes in yellow are the DSL Domain classes, those classes are the domain model of the DSL, who describe the structure of the problem, those classes should be able to store all the 'problem information'.

The Red boxes are the shapes that are related to the domain model.
A neuron is represented by a Circle.
A Connection should be represented by a line in a certain color etc...
DSL let's you do lot's of fun things with it.

I’ve drawn in Visio how the designer itself should look like, this is an approximate drawing, but it's very close to the reality.
When my designer is finished, I’ll put some screenshots online, and maybe also some code.



This is how it looks like in visual studio (in the experimental hive):



This example shows the structure of a back propagation network that's able to solve the famous XOR problem. This problem is also addressed in another entry of mine regarding the Neural Network Simulator.

The Designer I’m building uses in fact the same framework, with the difference that there is an extra factory for creating the network, and the framework is extended with extended methods of the .NET 3.5 network.

Friday, March 14, 2008

Object Cloning Using IL in C#

This subject is inspired on a session I followed on the TechDays 2008,
that addressed the fact that IL (Intermediate Language) can be used to clone objects, among other things, and that it's not evil at all, and it can be pretty performant also.

You only have to see that you don't overuse it tho, because otherwise the readability of your code is reduced, which is not a good thing for the maintenance.
And wrong usage of reflection (what IL is, or at least uses) can also result in poor performance.

This being said, I tested this on my own, with some self written code and comments,
just to test that it was really true what Mr. Roy Osherove told me ;)

Below you can see a screenshot of a console application that does the tests.



As you see, sometimes the IL code is even faster the the normal cloning on a simple class Person with a couple of fields in it that are filled in with some random values.

Let's take a look at the code, first we declare a simple class Person
I've added comments in the code so that people that don't understand IL too much, can also understand what's happening.

Person class definition:

public class Person
{
private int _id;
private string _name;
private string _firstName;
private string _field1, _field2, _field3;

public Person()
{

}

public int ID
{
get { return _id; }
set { _id = value; }
}

public string Name
{
get { return _name; }
set { _name = value; }
}

public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
}




TestCode:

The code below is a nice piece of code, read through the comments and you'll understand what's stated.


class Program
{
/// <summary>
/// Delegate handler that's used to compile the IL to.
/// (This delegate is standard in .net 3.5)
/// </summary>
/// <typeparam name="T1">Parameter Type</typeparam>
/// <typeparam name="TResult">Return Type</typeparam>
/// <param name="arg1">Argument</param>
/// <returns>Result</returns>
public delegate TResult Func<T1, TResult>(T1 arg1);
/// <summary>
/// This dictionary caches the delegates for each 'to-clone' type.
/// </summary>
static Dictionary<Type, Delegate> _cachedIL = new Dictionary<Type, Delegate>();

/// <summary>
/// The Main method that's executed for the test.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
DoCloningTest(1000);
DoCloningTest(10000);
DoCloningTest(100000);
//Commented because the test takes long ;)
//DoCloningTest(1000000);

Console.ReadKey();
}

/// <summary>
/// Do the cloning test and printout the results.
/// </summary>
/// <param name="count">Number of items to clone</param>
private static void DoCloningTest(int count)
{
// Create timer class.
HiPerfTimer timer = new HiPerfTimer();
double timeElapsedN = 0, timeElapsedR = 0, timeElapsedIL = 0;

Console.WriteLine("--> Creating {0} objects...", count);
timer.StartNew();
List<Person> personsToClone = CreatePersonsList(count);
timer.Stop();
Person temp = CloneObjectWithIL(personsToClone[0]);
temp = null;
Console.WriteLine("\tCreated objects in {0} seconds", timer.Duration);

Console.WriteLine("- Cloning Normal...");
List<Person> clonedPersons = new List<Person>(count);
timer.StartNew();
foreach (Person p in personsToClone)
{
clonedPersons.Add(CloneNormal(p));
}
timer.Stop();
timeElapsedN = timer.Duration;

Console.WriteLine("- Cloning IL...");
clonedPersons = new List<Person>(count);
timer.StartNew();
foreach (Person p in personsToClone)
{
clonedPersons.Add(CloneObjectWithIL<Person>(p));
}
timer.Stop();
timeElapsedIL = timer.Duration;

Console.WriteLine("- Cloning Reflection...");
clonedPersons = new List<Person>(count);
timer.StartNew();
foreach (Person p in personsToClone)
{
clonedPersons.Add(CloneObjectWithReflection(p));
}
timer.Stop();
timeElapsedR = timer.Duration;

Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("----------------------------------------");
Console.WriteLine("Object count:\t\t{0}", count);
Console.WriteLine("Cloning Normal:\t\t{0:00.0000} s", timeElapsedN);
Console.WriteLine("Cloning IL:\t\t{0:00.0000} s", timeElapsedIL);
Console.WriteLine("Cloning Reflection:\t{0:00.0000} s", timeElapsedR);
Console.WriteLine("----------------------------------------");
Console.ResetColor();
}

/// <summary>
/// Create a list of persons with random data and a given number of items.
/// </summary>
/// <param name="count">Number of persons to generate</param>
/// <returns>List of generated persons</returns>
private static List<Person> CreatePersonsList(int count)
{
Random r = new Random(Environment.TickCount);
List<Person> persons = new List<Person>(count);
for (int i = 0; i < count; i++)
{
Person p = new Person();
p.ID = r.Next();
p.Name = string.Concat("Slaets_", r.Next());
p.FirstName = string.Concat("Filip_", r.Next());
persons.Add(p);
}
return persons;
}

/// <summary>
/// Clone one person object with reflection
/// </summary>
/// <param name="p">Person to clone</param>
/// <returns>Cloned person</returns>
private static Person CloneObjectWithReflection(Person p)
{
// Get all the fields of the type, also the privates.
FieldInfo[] fis = p.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
// Create a new person object
Person newPerson = new Person();
// Loop through all the fields and copy the information from the parameter class
// to the newPerson class.
foreach (FieldInfo fi in fis)
{
fi.SetValue(newPerson, fi.GetValue(p));
}
// Return the cloned object.
return newPerson;
}

/// <summary>
/// Generic cloning method that clones an object using IL.
/// Only the first call of a certain type will hold back performance.
/// After the first call, the compiled IL is executed.
/// </summary>
/// <typeparam name="T">Type of object to clone</typeparam>
/// <param name="myObject">Object to clone</param>
/// <returns>Cloned object</returns>
private static T CloneObjectWithIL<T>(T myObject)
{
Delegate myExec = null;
if (!_cachedIL.TryGetValue(typeof(T), out myExec))
{
// Create ILGenerator
DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
ConstructorInfo cInfo = myObject.GetType().GetConstructor(new Type[] { });

ILGenerator generator = dymMethod.GetILGenerator();

LocalBuilder lbf = generator.DeclareLocal(typeof(T));
//lbf.SetLocalSymInfo("_temp");

generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc_0);
foreach (FieldInfo field in myObject.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
{
// Load the new object on the eval stack... (currently 1 item on eval stack)
generator.Emit(OpCodes.Ldloc_0);
// Load initial object (parameter) (currently 2 items on eval stack)
generator.Emit(OpCodes.Ldarg_0);
// Replace value by field value (still currently 2 items on eval stack)
generator.Emit(OpCodes.Ldfld, field);
// Store the value of the top on the eval stack into the object underneath that value on the value stack.
// (0 items on eval stack)
generator.Emit(OpCodes.Stfld, field);
}

// Load new constructed obj on eval stack -> 1 item on stack
generator.Emit(OpCodes.Ldloc_0);
// Return constructed object. --> 0 items on stack
generator.Emit(OpCodes.Ret);

myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
_cachedIL.Add(typeof(T), myExec);
}
return ((Func<T, T>)myExec)(myObject);
}

/// <summary>
/// Clone a person object by manually typing the copy statements.
/// </summary>
/// <param name="p">Object to clone</param>
/// <returns>Cloned object</returns>
private static Person CloneNormal(Person p)
{
Person newPerson = new Person();
newPerson.ID = p.ID;
newPerson.Name = p.Name;
newPerson.FirstName = p.FirstName;
return newPerson;
}
}


The basic thing that it does is, create a DynamicMethod, get the ILGenerator, emit code in the method, compile it to a delegate, and execute the delegate.
The delegate is cached so that the IL is not generated each time a cloning should take place, so we loose only one time performance, when the first object is cloned (the IL has to be created and compiled at runtime).

Hopefully this article is of use for some people, if so, let me know.

Regards

Friday, February 29, 2008

Top 16 excuses for a software developer

When a software tester comes up to your desk and complains about a bug, and you have no idea what to tell him, just use the following list and pick a number:

1. This has always worked fine...
2. I didn't change anything...
3. It works here!
4. Weird, I tested this!
5. My unit tests were all green!
6. Are you using the latest build?
7. Someone probably changed the code.
8. I've never seen this error before...
9. That's not my code!
10. I think this has never worked before...
11. The database changes probably haven't been done yet.
12. Was that included in the functional design?
13. I don't know
14. You're not using Internet Explorer, are you?
15. It's not a bug, it's a feature!
16. Must be something you did wrong..

Thanks Alex for your contribution of the last 3 items ;)

Saturday, December 1, 2007

Neural Network Simulation


Since I'm bitten by the A.I. microbe, I'm experimenting with all kinds of Neural Networks and Genetic Algorithms.
Therefore I created a little simulator to visualize the networks, for experimenting with it.

For the visualizing I used GLEE , a 'new' framework from Microsoft Research that's able to draw flowcharts, hierarchical diagrams, ...

I created the tool so that I can manage which network type that I want to simulate.

image

In this case I'll choose for the Back Propagation network.
The BP-Network is a network that 'learns' from it's mistakes,
it's trained supervised, meaning that the network knows it's input values and the expected output values for the given inputs.
In the scheme below you can see the layout of a BP-Network.
1 and 0 are the input nodes, who accepts numeric values.
2,3,4 are the hidden layers, expanding the learning possibilities
5 is the output node, which gives a number, and you'll probably have to round it to have a significant result. 

We can adjust the number of input , hidden and output-nodes.

image

Next after defining the network parameters we'll have to provide some input data.
The upper group-box contains the input values, the lower box contains the output values, the amount of values for input and output must be the same, ex. 4 inputs = 4 outputs. (Pretty normal I think)

So in the example below we provide input and output values for the famous XOR-problem.

Input 1 Input 2 Output
0 0 0
0 1 1
1 0 1
1 1 0

The table shows the input for the XOR problem, this data pattern is also inserted in the application below.

image

After the data-input we'll go to the training part.
Here we'll train the network until all inputs give the right output,


image

and re-train it until the data-errors are 0. meaning that the output-values are more accurate, or should be more accurate to 0.0 or 1.0 (or the output values you trained it against).

image

After the training happened, we'll test the network.
As you can see below, in the application you can provide your own input pattern.
In this example we provide 0 and 1 and the output is 0.95, which is rounded -> 1.

If you look at the table above then indeed the output is 1 when the inputs are 0 and 1.

image

Currently I'm working on the 3 other network types to simulate, BAM, SON and Adeline,
when that part is finished, then I'll make the tool public so that more people can play with it, and understand the working of neural networks better.

I'm also going to include the possibility to remove node links, so that you can train an unbalanced network, which sometimes is trained quicker and can give more accurate output. But that process is more like trail and error :).

Hope you enjoyed this introduction, if you have any questions or comments, don't hesitate to leave them behind or to mail them to me.

Regards,

F.

Wednesday, September 12, 2007

Localization of a complete PropertyGrid in .NET

 

While developing an application for my company, I had to use a PropertyGrid (default WinForms-usercontrol).

The problem was the localization of this grid.
The client wanted to localize the categories, property-names,  enum selection values and the descriptions.

So I did some research and developed a couple of handy classes that can do the job.

The first step was to create some resx resource files:

  • EngineProperties.resx
    Used for Description and Categories
  • EngineProperties.nl.resx
  • EngineProperties.fr.resx
  • PropertyNames.resx
    Used for enum-value- and propertyname localizations.
  • PropertyNames.nl.resx

( I created them by clicking on the project and adding a new resource file )

The first class we need is a class to localize the descriptions visible in the PropertyGrid control.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.ComponentModel;
   5:  
   6: namespace Whizzo3D.Engine.Localization
   7: {
   8:     /// <summary>
   9:     /// Specifies a description for a property or event.
  10:     /// Localized version of the description class.
  11:     /// Used to localize messages in a propertygrid for example.
  12:     /// </summary>
  13:     public class GlobalDescriptionAttribute : DescriptionAttribute
  14:     {
  15:         public GlobalDescriptionAttribute(string descriptionKey)
  16:             : base()
  17:         {
  18:             // Set the value to the localized description of our key.
  19:             base.DescriptionValue = 
  20:                 EngineProperties.ResourceManager.GetString(descriptionKey);
  21:         }
  22:     }
  23: }

The second class we need is a class to localize the categories in the PropertyGrid control.



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.ComponentModel;
   5:  
   6: namespace Whizzo3D.Engine.Localization
   7: {
   8:     /// <summary>
   9:     /// Specifies the name of the category in which to group the property or event
  10:     /// when displayed in a System.Windows.Forms.PropertyGrid control set to Categorized
  11:     /// mode.
  12:     /// </summary>
  13:     public class GlobalCategoryAttribute : CategoryAttribute
  14:     {
  15:         public GlobalCategoryAttribute(string categoryKey)
  16:             : base(categoryKey)
  17:         {
  18:     
  19:         }
  20:  
  21:         /// <summary>
  22:         /// Fetch a localized string for the culture defined
  23:         /// in the Thread.CurrentThread.CurrentUICulture
  24:         /// </summary>
  25:         /// <param name="value">Key to fetch value for</param>
  26:         /// <returns>Requested value.</returns>
  27:         protected override string GetLocalizedString(string value)
  28:         {
  29:             return EngineProperties.ResourceManager.GetString(value);
  30:         }
  31:     }
  32: }

Image a test-class InfoMessage for using the attributes above:
(This class is just a class from my project, look at the attributes located above the property declarations of Message and Title)



   1: /// <summary>
   2: /// Respresents an information-message about the domain,
   3: /// that appears to the screen when the domain is loaded.
   4: /// </summary>
   5: [DataContract()]
   6: [Serializable]
   7: public class InfoMessage : Concurrency
   8: {
   9:     #region Fields
  10:  
  11:     // ...
  12:  
  13:     #endregion
  14:  
  15:     #region Constructor(s)
  16:  
  17:     // ...
  18:  
  19:     #endregion
  20:  
  21:     #region Properties
  22:  
  23:     /// <summary>
  24:     /// Gets/sets the ID of the InfoMessage
  25:     /// </summary>
  26:     [Browsable(false)]    // This property does not appear in the PropertyGrid Control.
  27:     [DataMember]
  28:     public Guid IDInfoMessage
  29:     {
  30:         get { return _iDInfoMessage; }
  31:         set { _iDInfoMessage = value; }
  32:     }
  33:  
  34:     /// <summary>
  35:     /// Gets/sets the message.
  36:     /// </summary>
  37:     [DataMember]
  38:     [GlobalDescription("InfoMessage_Message"), GlobalCategory("InfoMessage")]
  39:     public string Message
  40:     {
  41:         get { return _message; }
  42:         set { _message = value; }
  43:     }
  44:  
  45:     /// <summary>
  46:     /// Gets/sets the title.
  47:     /// </summary>
  48:     [DataMember]
  49:     [GlobalDescription("InfoMessage_Title"), GlobalCategory("InfoMessage")]
  50:     public string Title
  51:     {
  52:         get { return _title; }
  53:         set { _title = value; }
  54:     }
  55:  
  56:     #endregion
  57: }

The third class/part is to create some classes that can localize the Property-names displayed in the PropertyGrid. This is a little bit nifty because the control uses reflection to get the property-names that it's going to display.
The client want to replace the property-name 'Message' with the localized dutch value 'Bericht:' for example.

The things we must do to get this job done:



  1. Create an attribute for defining which property to change
    It's not needed when the values are formatted in a certain way in the resource file.
    The format is: classname_propertyname

  2. Create a custom PropertyDescriptor to fetch the localized description values.

  3. Create an object that implements ICustomTypeDescriptor so that the 'GetProperties' method is overridden. This method is called by the PropertyGrid when it requests the names of the properties that it wants do display.

This knowing, we can start programming the actual classes.


The Attribute (1):



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4:  
   5: namespace Whizzo3D.Engine.Localization
   6: {
   7:     /// <summary>
   8:     /// Defines an attribute for giving a key to localize the propertyname itself.
   9:     /// </summary>
  10:     /// <remarks>
  11:     /// We don't need this attribute of the keys are in the form of
  12:     /// classname_propertyname in the resource file.
  13:     /// </remarks>
  14:     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
  15:     public class GlobalPropertyAttribute : Attribute
  16:     {
  17:         private string _resourceKey = "";
  18:  
  19:         public GlobalPropertyAttribute()
  20:         {
  21:             _resourceKey = string.Empty;
  22:         }
  23:  
  24:         public GlobalPropertyAttribute(string nameKey)
  25:         {
  26:             _resourceKey = nameKey;
  27:         }
  28:  
  29:         public String NameKey
  30:         {
  31:             get { return _resourceKey; }
  32:             set { _resourceKey = value; }
  33:         }
  34:     }
  35: }

The PropertyDescriptor (2):



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.ComponentModel;
   5:  
   6: namespace Whizzo3D.Engine.Localization
   7: {
   8:     /// <summary>
   9:     /// GlobalizedPropertyDescriptor enhances the base class obtaining the display name for a property
  10:     /// from the resource.
  11:     /// </summary>
  12:     public class GlobalPropertyDescriptor : PropertyDescriptor
  13:     {
  14:         private PropertyDescriptor basePropertyDescriptor;
  15:         private string localizedName = "";
  16:  
  17:         #region Constructor(s)
  18:  
  19:         public GlobalPropertyDescriptor(PropertyDescriptor basePropertyDescriptor)
  20:             : base(basePropertyDescriptor)
  21:         {
  22:             // Set the property-descriptor where we work on.
  23:             this.basePropertyDescriptor = basePropertyDescriptor;
  24:         }
  25:  
  26:         #endregion
  27:  
  28:         #region Abstract Dummy Methods
  29:  
  30:         public override bool CanResetValue(object component)
  31:         {
  32:             return basePropertyDescriptor.CanResetValue(component);
  33:         }
  34:  
  35:         public override Type ComponentType
  36:         {
  37:             get { return basePropertyDescriptor.ComponentType; }
  38:         }
  39:  
  40:         public override object GetValue(object component)
  41:         {
  42:             return this.basePropertyDescriptor.GetValue(component);
  43:         }
  44:  
  45:         public override bool IsReadOnly
  46:         {
  47:             get { return this.basePropertyDescriptor.IsReadOnly; }
  48:         }
  49:  
  50:         public override string Name
  51:         {
  52:             get { return this.basePropertyDescriptor.Name; }
  53:         }
  54:  
  55:         public override Type PropertyType
  56:         {
  57:             get { return this.basePropertyDescriptor.PropertyType; }
  58:         }
  59:  
  60:         public override void ResetValue(object component)
  61:         {
  62:             this.basePropertyDescriptor.ResetValue(component);
  63:         }
  64:  
  65:         public override bool ShouldSerializeValue(object component)
  66:         {
  67:             return this.basePropertyDescriptor.ShouldSerializeValue(component);
  68:         }
  69:  
  70:         public override void SetValue(object component, object value)
  71:         {
  72:             this.basePropertyDescriptor.SetValue(component, value);
  73:         }
  74:  
  75:         public override string Description
  76:         {
  77:             get { return basePropertyDescriptor.Description; }
  78:         }
  79:  
  80:         #endregion
  81:  
  82:         #region Abstract Overrides
  83:  
  84:         /// <summary>
  85:         /// Gets the displayname for a property
  86:         /// </summary>
  87:         public override string DisplayName
  88:         {
  89:             get
  90:             {
  91:                 // Get the propertyName from the resources file.
  92:                 // For this 3D Engine it is 'PropertyNames'
  93:  
  94:                 // The displaynameKey for this property (localized)
  95:                 string displayNameKey = string.Empty;
  96:                 // Look for the defined attribute 'GlobalizedPropertyAttribute'
  97:                 foreach (Attribute oAttrib in this.basePropertyDescriptor.Attributes)
  98:                 {
  99:                     if (oAttrib.GetType().Equals(typeof(GlobalPropertyAttribute)))
 100:                     {
 101:                         displayNameKey = (oAttrib as GlobalPropertyAttribute).NameKey;
 102:                         break;
 103:                     }
 104:                 }
 105:  
 106:                 if (string.IsNullOrEmpty(displayNameKey))
 107:                     displayNameKey = basePropertyDescriptor.DisplayName;
 108:  
 109:                 this.localizedName = PropertyNames.ResourceManager.GetString(displayNameKey);
 110:  
 111:                 if (string.IsNullOrEmpty(this.localizedName))
 112:                     this.localizedName = basePropertyDescriptor.DisplayName;
 113:  
 114:                 return this.localizedName;
 115:             }
 116:         }
 117:  
 118:         #endregion
 119:     }
 120: }

The base-class where we have to inherit from with the localizable objects (3):



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.ComponentModel;
   5: using System.Resources;
   6:  
   7: using Whizzo3D.Engine.Localization;
   8:  
   9: namespace Whizzo3D.Engine.Objects
  10: {
  11:     /// <summary>
  12:     /// This base-object implements ICustomTypeDescriptor.
  13:     /// The main-task of this object is to instatiate our own
  14:     /// specialized property descriptor.
  15:     /// </summary>
  16:     public abstract class GlobalizedObject : ICustomTypeDescriptor
  17:     {
  18:         private PropertyDescriptorCollection _globalizedProps;
  19:  
  20:         /// <summary>
  21:         /// Instantiate a new localized object.
  22:         /// </summary>
  23:         protected GlobalizedObject()
  24:         {
  25:             // Default constructor
  26:         }
  27:  
  28:         #region ICustomTypeDescriptor Members
  29:  
  30:         public string GetClassName()
  31:         {
  32:             string className = TypeDescriptor.GetClassName(this, true);
  33:             return className;
  34:         }
  35:  
  36:         public AttributeCollection GetAttributes()
  37:         {
  38:             return TypeDescriptor.GetAttributes(this, true);
  39:         }
  40:  
  41:         public String GetComponentName()
  42:         {
  43:             return TypeDescriptor.GetComponentName(this, true);
  44:         }
  45:  
  46:         public TypeConverter GetConverter()
  47:         {
  48:             return TypeDescriptor.GetConverter(this, true);
  49:         }
  50:  
  51:         public EventDescriptor GetDefaultEvent()
  52:         {
  53:             return TypeDescriptor.GetDefaultEvent(this, true);
  54:         }
  55:  
  56:         public PropertyDescriptor GetDefaultProperty()
  57:         {
  58:             return TypeDescriptor.GetDefaultProperty(this, true);
  59:         }
  60:  
  61:         public object GetEditor(Type editorBaseType)
  62:         {
  63:             return TypeDescriptor.GetEditor(this, editorBaseType, true);
  64:         }
  65:  
  66:         public EventDescriptorCollection GetEvents(Attribute[] attributes)
  67:         {
  68:             return TypeDescriptor.GetEvents(this, attributes, true);
  69:         }
  70:  
  71:         public EventDescriptorCollection GetEvents()
  72:         {
  73:             return TypeDescriptor.GetEvents(this, true);
  74:         }
  75:  
  76:         /// <summary>
  77:         /// Called to get the properties of a type.
  78:         /// </summary>
  79:         /// <param name="attributes"></param>
  80:         /// <returns></returns>
  81:         public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
  82:         {
  83:             if (_globalizedProps == null)
  84:             {
  85:                 // Get the collection of properties
  86:                 PropertyDescriptorCollection baseProps = TypeDescriptor.GetProperties(this, attributes, true);
  87:  
  88:                 _globalizedProps = new PropertyDescriptorCollection(null);
  89:  
  90:                 // For each property use a property descriptor of our own that is able to be globalized
  91:                 for (int i = 0; i < baseProps.Count; i++)
  92:                     _globalizedProps.Add(new GlobalPropertyDescriptor(baseProps[i]));
  93:             }
  94:             return _globalizedProps;
  95:         }
  96:  
  97:         public PropertyDescriptorCollection GetProperties()
  98:         {
  99:             // Only do once
 100:             if (_globalizedProps == null)
 101:             {
 102:                 // Get the collection of properties
 103:                 PropertyDescriptorCollection baseProps = TypeDescriptor.GetProperties(this, true);
 104:                 _globalizedProps = new PropertyDescriptorCollection(null);
 105:  
 106:                 // For each property use a property descriptor of our own that is able to be globalized
 107:                 foreach (PropertyDescriptor oProp in baseProps)
 108:                 {
 109:                     _globalizedProps.Add(new GlobalPropertyDescriptor(oProp));
 110:                 }
 111:             }
 112:             return _globalizedProps;
 113:         }
 114:  
 115:         public object GetPropertyOwner(PropertyDescriptor pd)
 116:         {
 117:             return this;
 118:         }
 119:  
 120:         #endregion
 121:     }
 122:  
 123: }

Now, this in mind, look back at the InfoMessage test-class. It inherits from Concurrency (it's an object of me to keep track of concurrency), and the Concurrency object inherits from the GlobalizedObject.


As far as the implementions it's done. Now we have to adjust the resource file(s).


The PropertyDescriptor uses the PropertyNames resource file(s), to get the localized values for the keys.
The descriptor makes it's own keys, all you have to is fill them in in the resource file.
Something like this:




If you supply an InfoMessage object to the PropertyGrid, then it will be localized.


The only problem now is the localization of enum-values if you have enums in the class that you want to supply to the PropertyGrid.


For localizing the enum-values, we need a TypeConverter, more specific the EnumConverter from .NET
We have to inherit from it to create our own converter.
And we have to cache the relation between the localized values and the original enum-values.


The converter looks like this:



   1: /// <summary>
   2: /// This class is a converter for enums.
   3: /// Purpose is to localize enum values.
   4: /// </summary>
   5: public class GlobalEnumConverter : EnumConverter
   6: {
   7:     Dictionary<CultureInfo, Dictionary<string, object>> _lookupTables;
   8:  
   9:     /// <summary>
  10:     /// Instantiate a new Enum Converter
  11:     /// </summary>
  12:     /// <param name="type">Type of the enum to convert</param>
  13:     public GlobalEnumConverter(Type type)
  14:         : base(type)
  15:     {
  16:         _lookupTables = new Dictionary<CultureInfo, Dictionary<string, object>>();
  17:     }
  18:  
  19:     /// <summary>
  20:     /// The lookuptable holds the references between the original values and the localized values.
  21:     /// </summary>
  22:     /// <param name="culture">Culture for which the localization pairs must be fetched (or created)</param>
  23:     /// <returns>Dictionary</returns>
  24:     private Dictionary<string, object> GetLookupTable(CultureInfo culture)
  25:     {
  26:         Dictionary<string, object> result = null;
  27:         if (culture == null)
  28:             culture = CultureInfo.CurrentCulture;
  29:  
  30:         if (!_lookupTables.TryGetValue(culture, out result))
  31:         {
  32:             result = new Dictionary<string, object>();
  33:             foreach (object value in GetStandardValues())
  34:             {
  35:                 string text = ConvertToString(null, culture, value);
  36:                 if (text != null)
  37:                 {
  38:                     result.Add(text, value);
  39:                 }
  40:             }
  41:             _lookupTables.Add(culture, result);
  42:         }
  43:         return result;
  44:     }
  45:  
  46:     /// <summary>
  47:     /// Convert the localized value to enum-value
  48:     /// </summary>
  49:     /// <param name="context"></param>
  50:     /// <param name="culture"></param>
  51:     /// <param name="value"></param>
  52:     /// <returns></returns>
  53:     public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
  54:     {
  55:         if (value is string)
  56:         {
  57:             Dictionary<string, object> lookupTable = GetLookupTable(culture);
  58:             //LookupTable lookupTable = GetLookupTable(culture);
  59:             object result = null;
  60:             if (!lookupTable.TryGetValue(value as string, out result))
  61:             {
  62:                 result = base.ConvertFrom(context, culture, value);
  63:             }
  64:             return result;
  65:             //return base.ConvertFrom(context, culture, value);
  66:         }
  67:         else
  68:         {
  69:             return base.ConvertFrom(context, culture, value);
  70:         }
  71:     }
  72:  
  73:     /// <summary>
  74:     /// Convert the enum value to a localized value
  75:     /// </summary>
  76:     /// <param name="context"></param>
  77:     /// <param name="culture"></param>
  78:     /// <param name="value"></param>
  79:     /// <param name="destinationType"></param>
  80:     /// <returns></returns>
  81:     public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  82:     {
  83:         if (value != null && destinationType == typeof(string))
  84:         {
  85:             Type type = value.GetType();
  86:             string resourceName = string.Format("{0}_{1}", type.Name, value.ToString());
  87:             string result = PropertyNames.ResourceManager.GetString(resourceName, culture);
  88:             if (result == null)
  89:                 result = resourceName;
  90:             return result;
  91:         }
  92:         else
  93:         {
  94:             return base.ConvertTo(context, culture, value, destinationType);
  95:         }
  96:     }
  97: }

If you want to use it, take your enum-declaration and put the TypeConverterAttribute above it:



   1: // The EnumMember and DataContract attributes are stuff from .NET 3.0...
   2: [Flags]
   3: [DataContract()]
   4: [Serializable]
   5: [TypeConverter(typeof(GlobalEnumConverter))]
   6: public enum StatusFlag : int
   7: {
   8:     [EnumMember]
   9:     None = 0,
  10:     [EnumMember]
  11:     Draft = 1,          // Kladwerk
  12:     [EnumMember]
  13:     Deleted = 2,        // Verwijderd
  14:     [EnumMember]
  15:     ToValidate = 4,     // Te valideren
  16:     [EnumMember]
  17:     Invalid = 8,        // Ongeldig
  18:     [EnumMember]
  19:     Valid = 16,         // Geldig
  20:     [EnumMember]
  21:     ToRevise = 32         // Te herbekijken
  22: }

Look at the resource image for seeing the declaration of the keys needed to localize this enum-values.


Mind the [TypeConverter(typeof(GlobalEnumConverter))] attribute on top of the declaration, that line makes sure that the converter is used when the PropertyGrid wants to display the enum-values.


Just change your Culture of you current thread and see that the PropertyGrid will change it's values ;)


Hope this helps, if there are any questions, you know where to find me.


Regards,


Whizzo