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 |
Monday, October 02, 2006 7:47:56 AM (New Zealand Daylight Time, UTC+13:00)
Alex,

I've been thinking.
We are both in Auckland, both interested in IronPython and both called Alex.
I think we've got enough in common to meet up and have a coffee!

Alex
Monday, October 02, 2006 8:28:41 PM (New Zealand Daylight Time, UTC+13:00)
Sounds like a plan - I pass through Ellerslie on the odd occasion, we could probably catch up for coffee there.
Comments are closed.
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