The final solution (for generic IronPython delegates)...

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 context = new Dictionary();
context.Add("user", "alex");
context.Add("age", 26);

Proc testCore = delegate
{
List parameters = new List(context.Keys);
List values = new List(context.Values);

PythonEngine engine = new PythonEngine();

Type[] parameterTypes = new Type[context.Count];
for (int i = 0; i < parametertypes.length;="" i++)="" parametertypes[i]="typeof">

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

Type pythonEngine = typeof (PythonEngine);
MethodInfo genericMethod =
pythonEngine.GetMethod("CreateMethod", new Type[] {typeof (string), typeof (IList)});
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.CreateMethodTDelegate>(string statements, IListstring> 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)
Written on October 1, 2006