Last post I promised that I would post the final code that I was
working on, in regards to Generics and DynamicMethods. The
basic idea of the code is a class that generates DynamicMethod
delegates that have been casted as a certain type of delegate, using
Generics. Here's what I was thinking the calling code would
look like
TestDelegate del = generator.SomeMethod<TestDelegate>();
Surprisingly, this was a lot hard than it first seems. It
seems that there are some restrictions with Generics and delegates.
First off, you can not use System.Delegate as a limiter for a
Generic type. So you can
not do the
following :
public T SomeMethod<T>() where T : System.Delegate
If you try to compile the previous code in some program you get the
following error message:
Constraint
cannot be special class 'System.Delegate'. Now
DynamicMethod's CreateDelegate will only return an object of
type System.Delegate. That is all and good, but if you are
trying to have type safe delegate, you need to cast that object into
the correct delegate type. But the following code will also
fail:
return (T)dynamicMethod.CreateDelegate(typeof(T));
That section of code will won't compile and will generate this error:
Cannot convert type
'System.Delegate' to 'T'. It seems that you have
to use the as keyword to do a proper cast.
return dynamicMethod.CreateDelegate(typeof(T)) as T;
Now the code won't compile because
The
type parameter 'T' cannot be used with the 'as' operator because it
does not have a class type constraint nor a 'class' constraint.
So you need to add a class constraint for T like
private T SomeMethod<T>() where T : class
Now once you set up your code in this way, the code will finally
compile. All this information about using Generics and
delegates in this way comes from Mike Woodring's post
Constraints
on contraints, I just applied his knowledge to DynamicMethods.
Here's the final class
public class MethodGenerator
{
public event EventHandler<CreatingMethodBodyEventArgs> CreatingMethodBody;
public MethodGenerator()
{}
/// <summary>
/// Generates a new method with a
signature that matches the signature of the delegate T.
/// </summary>
/// <typeparam name="T">The type of the delegate to return and
use as a method signature.</typeparam>
/// <typeparam name="K">The type of the object with which the
generated method will be associated.</typeparam>
/// <param name="name">The identifying name of the generated
method.</param>
/// <returns>A delegate of type T which points to
the generated method</returns>
public T Generate<T, K>(string name) where T : class
{
MethodInfo method = GetMethod(typeof(T));
ParameterInfo[] parameterDefinitions =
method.GetParameters();
Type[] parameters = new Type[parameterDefinitions.Length];
for (int index = 0; index <
parameterDefinitions.Length; index++)
{
parameters[index]
= parameterDefinitions[index].ParameterType;
}
return (ConstructDynamicMethod(name, method,
typeof(T), parameters, typeof(K)).CreateDelegate(typeof(T)) as T);
}
/// <summary>
/// Generates a new method with a
signature that matches the signature of the delegate T.
/// </summary>
/// <typeparam name="T">The type of the delegate to return and
use as a method signature.</typeparam>
/// <typeparam name="K">The type of the object with which the
generated method will be associated.</typeparam>
/// <param name="name">The identifying name of the generated
method.</param>
/// <param name="instance">The instance of type K to which the
method will be bound.</param>
/// <returns>A delegate of type T which points to
the generated method</returns>
public T Generate<T, K>(string name, K instance) where T : class
{
MethodInfo method = GetMethod(typeof(T));
ParameterInfo[] parameterDefinitions =
method.GetParameters();
Type[] parameters = new Type[parameterDefinitions.Length + 1];
parameters[0]
= typeof(K);
for (int index = 0; index <
parameterDefinitions.Length; index++)
{
parameters[index
+ 1] = parameterDefinitions[index].ParameterType;
}
return (ConstructDynamicMethod(name, method,
typeof(T), parameters, typeof(K)).CreateDelegate(typeof(T), instance) as T);
}
private DynamicMethod ConstructDynamicMethod(string name, MethodInfo method, Type delegateType, Type[] parameters, Type ownerType)
{
DynamicMethod dm = new DynamicMethod(name, method.ReturnType, parameters,
ownerType);
ILGenerator il = dm.GetILGenerator();
if (CreatingMethodBody != null)
{
CreatingMethodBody(this, new CreatingMethodBodyEventArgs(name, delegateType, ownerType, il));
}
else
{
il.Emit(OpCodes.Ret);
}
return dm;
}
private MethodInfo GetMethod(Type delegateType)
{
if (!delegateType.IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("Type T must be a delegate", "T");
}
return delegateType.GetMethod("Invoke");
}
}
public class CreatingMethodBodyEventArgs : EventArgs
{
private string _name;
private Type _delegateType;
private Type _ownerType;
private ILGenerator _generator;
public Type DelegateType
{
get { return _delegateType; }
}
public ILGenerator ILGenerator
{
get { return _generator; }
}
public Type OwnerType
{
get { return _ownerType; }
}
public string Name
{
get { return _name; }
}
public CreatingMethodBodyEventArgs(string name, Type delegateType, Type ownerType, ILGenerator ilGenerator)
{
_name
= name;
_generator
= ilGenerator;
_delegateType
= delegateType;
_ownerType
= ownerType;
}
}
In the Generate function the first generic type parameter specifies the
delegate to imitate. The second generic type parameter is the
"owner" of the generated function. The return value will be a
strongly typed delegate (no more Invoke(new object[]... stuff).
When it is time to generate the body of the method, the class
fires off an event (CreateingMethodBody), that allows the caller to
specify the body of the method. Unfortunately you still have
to use the ILGenerator class to generate the method body.
There are still some issues with the above code too.
1). If T is a type other than System.Delegate or
subclass, it will throw a runtime error not a compile time error.
This is because there is no way to constraint the type
parameter to only System.Delegate. It just isn't allowed.
2) If the caller of the class doesn't specify a
method body or specifies an incorrect body (wrong return type,
incorrect code, etc), the error won't be caught until the
CreateDelegate method is called on the DynamicMethod, so runtime again,
not compile time.
3) I still haven't thought of a good use for it ;)