Search code examples
c#delegatesinvokeargumentexceptionmethodinfo

CreateDelegate Error: System.ArgumentException Cannot bind to the target method


I have an app that takes the dll of an external app, look into it for a specified class and method. It then gets the methodinfo from this external method and tries to then Create a delegate via Delegate.CreateDelegate

I constantly get

System.ArgumentException: 'Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.'

I have gone and extracted the code a bit to make it easier to share and debug as well as write a small simple external app to read from. See the code below:

External App Example as Library (.Net Framework 4.8)

using System;

namespace MethodLib
{
    public class PrintText
    {
        public string Print(string textToPrint, int number)
        {
            return $"{ PrintPrivate(textToPrint) }: {number}";
        }

        public static string PrintStatic(string textToPrint)
        {
            return textToPrint;
        }

        public void PrintVoid(string textToPrint)
        {
            Console.WriteLine(textToPrint);
        }

        private string PrintPrivate(string textToPrint)
        {
            return $"This is { textToPrint }";
        }
    }
}

App to CreateDelegate

MethodInfo Creation

using System;
using System.Reflection;

namespace DelegateApp
{
    public class PluginSupport
    {
        public MethodInfo GetMethodInfo(string methodName, string externalLocation)
        {
            var instance = Activator.CreateInstance(Assembly.LoadFrom(externalLocation)
                .GetType("MethodLib.PrintText"));

            var methodInfo = instance.GetType()
                .GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);

            return methodInfo;
        }
    }
}

Create Delegate Part

namespace DelegateApp
{
    public class MethodGenerator
    {
        private PluginSupport _pluginSupport;

        public MethodGenerator()
        {
            _pluginSupport = new PluginSupport();
        }

        public MethodDetails Create(string methodName, string path)
        {
            var method = _pluginSupport.GetMethodInfo(methodName, path);

            if (Equals(method, null))
            {
                throw new KeyNotFoundException($"Method '{ methodName }' doesn't exist in class");
            }

            return new MethodDetails
            {
                MethodName = method.Name,
                ComponentName = method.DeclaringType.Name,
                FriendlyName = method.DeclaringType.Name,
                Parameters = method.GetParameters(),
                LogicalPath = method.DeclaringType.Assembly.Location,
                Method = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), method)
            };
        }
    }
}

What have I tried

So reading a lot of different post I gather that the call I am using

(Func<string>)Delegate.CreateDelegate(typeof(Func<string>), method) is actually meant for static methods only, and as I am interested in all the public methods I am missing a target/instance.

So from other examples, you need to create the instance and pass that in as well, so I used the var myInstance = Actovator.CreateInstance and then passed this variable in as well, ending up with the following

(Func<string>)Delegate.CreateDelegate(typeof(Func<string>), myInstance, method)

I have also tried to use this one

public static Delegate CreateDelegate(Type type, Type target, string method, bool ignoreCase);

All of this keeps throwing

System.ArgumentException: 'Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.'

The only time I get it to work, is when I do the following:

methodName = PrintStatic from external app

var methodInfo = instance.GetType()
                .GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);

var deleg = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>),null, method)

of course this is not what I want as this only does the static for me and I want the non-static as well. But even with this if I add BindingFlags.Instance to the mix the static will also throw the same error.

If I also remove BindingFlags.Instance and my methodName = Print, then methodInfo is null.

My Questions

  1. What am I not understanding/missing with regards to the Delegate.CreateDelegate?
  2. What code am I missing that this is not working as I am expecting?
  3. Is there a different way to do the same thing?
  4. From creating the Delegate I want to invoke it later in the code, but is there a penalty for just using the invoke directly on methodinfo instead of creating a delegate then invoking it?
  5. Why does methodinfo not give me my public non-static member if BindingFlags.Instance is omitted?

Solution

  • Thanks to @Charlieface, I realised my signature types were not corresponding to me creating the delegate.

    So what I finally ended up with in this example code was to do the following in MethodGenerator class

    1. Get the parameters from methodinfo
    2. Go through the params and add them to a list of Types and get the type of each param
    3. Build a func where I do not know the number of types it will need and replace the number with the amount of params I have from methodinfo + output type
    4. Check if method isstatic and based on this set it to
    methHead = method.IsStatic
                    ? Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), null, method)
                    : Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), instance, method);  
    

    This is a bit of elaborate code I guess, but it works and will need to refine it or drop it in the actual code base where we want to use it. But as @Charlieface mentioned if you don't know the type, there isn't much point to the delegate.

    Final piece of code

     public MethodDetails Create(string methodName, string path)
            {
                var method = _pluginSupport.GetMethodInfo(methodName, path);
    
                if (Equals(method, null))
                {
                    throw new KeyNotFoundException($"Method '{ methodName }' doesn't exist in class");
                }
    
                var instance = Activator.CreateInstance(method.DeclaringType);
    
                List<Type> types = new List<Type>();
                var methodPrams = method.GetParameters();
                
                foreach (var item in methodPrams)
                {
                    types.Add(Type.GetType(item.ParameterType.UnderlyingSystemType.FullName));
                }
    
                var funcType = typeof(Func<>);
    
                var delegateFunc = Type.GetType(funcType.FullName.Replace("1", (methodPrams.Length + 1).ToString()));
    
                Delegate methHead;
    
                    types.Add(typeof(string));
    
                methHead = method.IsStatic
                    ? Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), null, method)
                    : Delegate.CreateDelegate(delegateFunc.MakeGenericType(types.ToArray()), instance, method);          
    
                return new MethodDetails
                {
                    MethodName = method.Name,
                    ComponentName = method.DeclaringType.Name,
                    FriendlyName = method.DeclaringType.Name,
                    Parameters = method.GetParameters(),
                    LogicalPath = method.DeclaringType.Assembly.Location,
                    Method = methHead
                };
            }