Creating delegates during reflection for unknown types

Using reflection, .NET allows you to create delegates based on MethodInfo objects. Calling a delegate is a lot more performant than using Invoke as already discussed by Jon Skeet. His results showed a delegate invocation is 600 times as fast. Plenty of reason to go through the extra effort of creating and caching a delegate. However, in a real world scenario when adding behavior to an existing class by using reflection, you’ll quickly encounter several limitations when using CreateDelegate. This post shows you where, and how to easily work around them.

In my previous article I discussed covariance and contravariance. One of the limitations of CreateDelegate is it only allows you to create delegates according to those rules. This makes perfect sense, but isn’t always desirable in practical use cases.

How would you go about creating a delegate for any method which is attributed with CallThis when the exact type of the argument is unknown? You do know the method’s signature matches Action<T>, and only the correct type will ever be passed to the delegate.

class CallThisAttribute : Attribute { }
class SomeClass
{
    [CallThis]
    public void SomeMethod( AnyType type ) { ... }
}

...

SomeClass instance = new SomeClass();
MethodInfo methodToCall = instance.GetType().GetAttributedMethod( typeof( CallThisAttribute ) );

// The following would work, but during reflection we don't know about AnyType.
Action<AnyType> action
    = Delegate.CreateDelegate( typeof( Action<AnyType> ), instance, methodToCall );

// The following throws an ArgumentException, since the method can only be called with AnyType.
Action<object> unkownArgument
    = Delegate.CreateDelegate( typeof( Action<object> ), instance, methodToCall );

Creating this delegate isn’t impossible. This is how you would go about creating it ordinarily.

SomeClass instance = new SomeClass();
Action<object> unknownArgument = o => instance.SomeMethod( (AnyType)o );
unknownArgument( new AnyType() );  // This works ...
unknownArgument( 10 );  // ... but this will throw an InvalidCastException.

Can this downcast be generated at runtime? Yes, and the easiest approach seems to be by using expression trees. Instead of passing the type of the required delegate to create as an argument, I opted to use a generic approach. Its usage looks as follows:

Action<object> unknownArgument
    = DelegateHelper.CreateDowncastingDelegate<Action<object>>( instance, methodToCall );

Any possible delegate type can be passed. All parameters of the method, and return value are matched with those of the delegate, and casts are created where necessary.

public static TDelegate CreateDowncastingDelegate<TDelegate>( object instance, MethodInfo method )
{
    MethodInfo delegateInfo = MethodInfoFromDelegateType( typeof( TDelegate ) );

    // Create delegate original and converted arguments.
    var delegateTypes = delegateInfo.GetParameters().Select( d => d.ParameterType );
    var methodTypes = method.GetParameters().Select( m => m.ParameterType );
    var delegateArgumentExpressions
        = CreateDelegateArgumentExpressions( delegateTypes, methodTypes );

    // Create method call.
    Expression methodCall = Expression.Call(
        instance == null ? null : Expression.Constant( instance ),
        method,
        delegateArgumentExpressions.ConvertedArguments );

    // Convert return type when necessary.
    Expression convertedMethodCall
        = delegateInfo.ReturnType == method.ReturnType
              ? methodCall
              : Expression.Convert( methodCall, delegateInfo.ReturnType );

    return Expression.Lambda<TDelegate>(
        convertedMethodCall,
        delegateArgumentExpressions.OriginalArguments
        ).Compile();
}

I’m still not entirely satisfied with the name of the method. CreateDowncastingDelegate refers to the fact that downcasts are generated where necessary. Its usage looks more like an upcasted delegate however. If anyone can come up with a better name, be sure to let me know.

The entire source code can be found in the FCL Extension library. In there you can also find another method which will most likely be the subject of my next post.

Advertisements
  1. #1 by JBSnorro on September 3, 2016 - 6:33 pm

    I think this can be achieved in a cleaner fashion. The problem is that the type `AnyType` isn’t available at compile-time, in particular for the cast. But we can easily _make_ it available at compile-time by introducing a generic method and by calling that method as `MethodInfo.MakeGenericMethod(…).Invoke(…)`. If this returns a delegate, rather than having to be called on every delegate invocation, the performance issues Skeet alludes to should be solved.

    I’m talking about the following solution:

    “`C#
    public static Action ToActionOfObject(MethodInfo method, object instance)
    {
    var parameterType = method.GetParameters()[0].ParameterType;
    var covariantAction = typeof(Program).GetMethod(nameof(toActionOfObjectHelper), BindingFlags.NonPublic | BindingFlags.Static)
    .MakeGenericMethod(parameterType)
    .Invoke(null, new object[] { method, instance });
    return (Action)covariantAction;
    }
    private static Action toActionOfObjectHelper(MethodInfo method, object instance)
    {
    var action = (Action)Delegate.CreateDelegate(typeof(Action), instance, method);
    return (object argument) => action((T)argument);
    }“`

  1. Casting to less generic types « Whathecode

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: