Sunday, October 01, 2006

Back once more...

I decided to spend ten minutes implementing a version of the last post which didn't require us to generate a function definition for a python method with a generic number of parameters... it's actually not too hard.

First off, the key to solving this problem is to generate a Type for our custom delegate - for performance reasons you would want to cache the results, but we won't do that here... Also I think it would be more apropriate for the first 0->9 parameters that we just return a generic Func delegate (generic Func & Proc delegates are going to be part of .Net 3.0, as mentioned here - At the moment I use something similar from the RhinoCommons library, have a look here)

So, first off we build a helper method for generating our delegate - just heavy use of emit here, based off an example on MSDN:


public Type CreateCustomDelegate(Type returnType, params Type[] parameterTypes)

{

    AssemblyName assembly = new AssemblyName();

    assembly.Name = "CreateCustomDelegateAssembly";

 

    AssemblyBuilder assemblyBuilder =

        AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.RunAndSave);

 

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("TempModule");

 

    TypeBuilder typeBuilder =

        moduleBuilder.DefineType("TempDelegateType",

                                TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class |

                                TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeof (MulticastDelegate));

 

    ConstructorBuilder constructorBuilder =

        typeBuilder.DefineConstructor(

            MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public,

            CallingConventions.Standard, new Type[] {typeof (object), typeof (int)});

    constructorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);

 

    MethodBuilder methodBuilder =

        typeBuilder.DefineMethod("Invoke",

                                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot |

                                MethodAttributes.Virtual, returnType, parameterTypes);

    methodBuilder.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime);

 

    return typeBuilder.CreateType();

}


Basically it goes through the motions of building a delegate type, derived from MulticastDelegate with our selected output type and parameter types...

Now to put that type to use:


[Test]

public void WithCustomDelegate()

{

    Dictionary<string, object> context = new Dictionary<string, object>();

    context.Add("user", "alex");

    context.Add("age", 26);

 

    Proc testCore = delegate

        {

            List<string> parameters = new List<string>(context.Keys);

            List<object> values = new List<object>(context.Values);

 

            PythonEngine engine = new PythonEngine();

 

            Type[] parameterTypes = new Type[context.Count];

            for (int i = 0; i < parameterTypes.Length; i++) parameterTypes[i] = typeof (object);

 

            Type delegateType = CreateCustomDelegate(typeof (object), parameterTypes);

 

            Type pythonEngine = typeof (PythonEngine);

            MethodInfo genericMethod =

                pythonEngine.GetMethod("CreateMethod", new Type[] {typeof (string), typeof (IList<string>)});

            MethodInfo method = genericMethod.MakeGenericMethod(delegateType);

 

            object result =

                method.Invoke(engine,

                              new object[] {"return user + ' is ' + str(age) + ' years old'", parameters});

 

            Delegate myDelegate = (Delegate) result;

            Assert.AreEqual("alex is 26 years old", myDelegate.DynamicInvoke(values.ToArray()));

        };

 

    // try it with our two keys in the dictionary

 

    testCore();

 

    // try it with another key in the dictionary

 

    context.Add("monkey_colour", "brown");

    testCore();

}


Our test creates our custom delegate type for the number of parameters we have, and then we use that when invoking the PythonEngine.CreateMethod<TDelegate>(string statements, IList<string> parameters) method.

Here we have to do a little reflection, simply because we cant directly use a local variable of type "Type" as a generic type parameter, so we grab the generic method, set the parameter and then invoke it.

However in the end, as you can see we get a real Delegate, which we can use DynamicInvoke upon.

This is actually pretty handy, and it's certainly a lot more script-friendly, compared to the alternative of forcing users to look up values from context by index ie.

   
context['user'] + ' is ' + str(context['age']) + ' years old'

vs.

   
user + ' is ' + str(age) + ' years old'

I know what I prefer ;o)

posted @ Sunday, October 01, 2006 8:03:41 AM (New Zealand Daylight Time, UTC+13:00)    Comments [2] | Trackback |
Search
FeedCount

Tags...
Who am I?
Alex Henderson
Alex Henderson
Auckland, New Zealand
Managing Director at Dev|Defined Limited

"Self Confessed Coding Junky for 15 years"
View Alex Henderson's profile on LinkedIn
 
Mobile: +64-21-402-969
Email: bittercoder 'at' gmail 'dot' com
MSN: bittercoder_nz@hotmail
Skype: alex.devdefined
Navigation