2

I have the following type hierarchy:

public abstract class Parent { }
public class Child : Parent
{
    public Task SayAsync(string arg)
    {
        Console.WriteLine(arg);
        return Task.CompletedTask;
    }    
}

I need to achieve the following:

  • Create any instance of Parent at run-time (this is already solved by calling a Func<Parent> which I obtain elsewhere in the code)
  • Then invoke (on that instance) all the public methods (which always return a Task and accept a string as a parameter) passing in the arg which is not a constant.

The above exists in a hot-path therefore in order to improve performance I am resorting to Cached Delegates so at startup I will be creating the delegates which I will then cache and use when required.

Here is an example I have done explicitly for Child which works but I cannot figure out how to make the delegate accept a Parent (as I won't know the type at compile time).

// If I change Child to Parent, I get "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type"
private delegate Task Invoker(Child instance, string arg);

void Main()
{
    var instance = new Child(); // This will be obtained by calling a Func<Parent>
    var methodWithArg = instance.GetType().GetMethod("SayAsync");

    var func = GetDelegateWithArg(methodWithArg);

    func(instance, "Foo");
}

private static Invoker GetDelegateWithArg(MethodInfo method)
{
    object pointer = null;
    return (Invoker)Delegate.CreateDelegate(typeof(Invoker), pointer, method);
}

Any ideas or alternatives to help me achieve the goal is appreciated.

MaYaN
  • 6,683
  • 12
  • 57
  • 109

2 Answers2

2

You can try to generate delegates using Expression Trees instead:

public abstract class Parent { }

public class Child : Parent
{
    public Task SayAsync(string arg)
    {
        Console.WriteLine(arg);
        return Task.CompletedTask;
    }
}

public delegate Task Invoker(Parent instance, string arg);

class Program
{
    static void Main(string[] args)
    {
        Parent instance = new Child(); // This will be obtained by calling a Func<Parent>
        var methodWithArg = instance.GetType().GetMethod("SayAsync");

        var func = GetDelegateWithArg(methodWithArg);

        func(instance, "Foo");
    }

    private static Invoker GetDelegateWithArg(MethodInfo method)
    {
        var instanceParameter = Expression.Parameter(typeof(Parent));
        var argParameter = Expression.Parameter(typeof(string));
        var body = Expression.Call(
            Expression.Convert(instanceParameter, method.DeclaringType),
            method,
            argParameter);
        var lambda = Expression.Lambda<Invoker>(body, instanceParameter, argParameter);
        return lambda.Compile();
    }
}

The generated delegates are still very fast, though may be a little bit slower than created by Delegate.CreateDelegate() method.

Pavel
  • 910
  • 5
  • 6
  • This is awesome! In fact I managed to compensate the extra overhead by marking the assembly as `[assembly: SecurityTransparent]` taken from [HERE](http://stackoverflow.com/a/5160513/1226568) It's now even faster than the `Delegate.CreateDelegate` version :-) – MaYaN Dec 02 '16 at 13:06
1

Maybe to try to pass target instance to GetDelegateWithArg method and change signature of the Invoker to receive just argument. Something like this:

private delegate Task Invoker(string arg);

private static Invoker GetDelegateWithArg(MethodInfo method, object target)
{            
    return (Invoker)Delegate.CreateDelegate(typeof(Invoker), target, method);
}

After suggested change you can pass instance to delegate creator and incoke func just with one argument:

func("Foo");
Johnny
  • 8,939
  • 2
  • 28
  • 33
  • That's an interesting answer but one problem is that I need each invocation to execute on a given instance, in your solution I have to create the `Delegate` every time I create one instance. No? – MaYaN Dec 02 '16 at 12:57
  • Yes, you have to create delegate for every instance. By providing the instance you will ensure that method will be created on instance level and later execute on a given instance. You can look at [link](https://msdn.microsoft.com/en-us/library/74x8f551(v=vs.110).aspx). – Johnny Dec 02 '16 at 13:17
  • Thanks for the clarification, I won't be able to use this method due to the performance const of creating the delegate for every instance. But upvoted regardless. @Pavel's answer is so far what I am looking for. – MaYaN Dec 02 '16 at 13:25