1

I'm using IronPython and I know how to expose methods from my class to the script's scope:

m_scope.SetVariable("log", new Action<string>(Log));

public void Log(string a)
{
    Console.WriteLine(a);
}

However, instead of calling SetVariable everytime I want to speed up the process using reflection. So, I created an attribute called ScriptMethodAttribute:

public sealed class ScriptMethodAttribute : Attribute
{
    public string Name { get; private set; }

    public ScriptMethodAttribute(string name)
    {
        Name = name;
    }
}

That way, I can define methods inside my class for the script to use, like so:

[ScriptMethod("log")]
public void Log(string a)
{
    Console.WriteLine(a);
}

Now I want to call SetVariable on every method that uses this attribute to speed up the process. However, that doesn't seem to work.

This is a utility method that returns a list of Tuple<ScriptMethodAttribute, MethodInfo.

public static IEnumerable<Tuple<TAttribute, MethodInfo>> FindMethodsByAttribute<TAttribute>()
        where TAttribute : Attribute
{
    return (from method in AppDomain.CurrentDomain.GetAssemblies()
                    .Where(assembly => !assembly.GlobalAssemblyCache)
                    .SelectMany(assembly => assembly.GetTypes())
                    .SelectMany(type => type.GetMethods())
                let attribute = Attribute.GetCustomAttribute(method, typeof(TAttribute), false) as TAttribute
                where attribute != null
                select new Tuple<TAttribute, MethodInfo>(attribute, method));
}

This is located in my script's class constructor:

foreach (var a in Reflector.FindMethodsByAttribute<ScriptMethodAttribute>())
{
    Action action = (Action)Delegate.CreateDelegate(typeof(Action), this, a.Item2);

    m_scope.SetVariable(a.Item1.Name, action);
}

I'm receiving the following exception:

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

I'm guessing it's because I have to include the required types in the Action constructor, but I don't know how to get them from the MethodInfo class.

Gilbert Williams
  • 175
  • 1
  • 10

1 Answers1

0

To handle methods with one parameter, you can use this code:

var parameters = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray();

var delegateType = typeof(Action<>).MakeGenericType(parameters);

var action = methodInfo.CreateDelegate(delegateType, this);

// Now you can call m_scope.SetVariable with action

Things get more tricky if you want to handle methods with any number of parameters, because you need to add some branching depending on the number of elements in the parameters array. If there's two elements then you need to use Action<,> instead of Action<>, if there's three you need to use Action<,,>, and so on.

A not-so-elegant but quick way would be to preallocate an array with each type of Action:

private Type[] DelegateTypes = new[]
{
    typeof (Action),
    typeof (Action<>),
    typeof (Action<,>),
    typeof (Action<,,>),
    typeof (Action<,,,>),
    typeof (Action<,,,,>),
    typeof (Action<,,,,,>),
    typeof (Action<,,,,,,>),
    typeof (Action<,,,,,,,>),
    typeof (Action<,,,,,,,,>),
    typeof (Action<,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,,,,,>),
    typeof (Action<,,,,,,,,,,,,,,,>)
};

From there, it's just a matter of accessing the right index of the array, depending on your number of parameters:

Type delegateType;

if (parameters.Length == 0)
    delegateType = DelegateTypes[0];
else
    delegateType = DelegateTypes[parameters.Length].MakeGenericType(parameters);
Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
  • And what if my method require no parameters at all? Then how would I get the type? Also, is there a way to get the type based on the parameter count so I can extend this to any parameter count I want and not limit myself to one? – Gilbert Williams Oct 16 '16 at 08:31
  • Ok, I see. That's what I was thinking about doing. Definitely not elegant but very smart. – Gilbert Williams Oct 16 '16 at 08:32
  • When I'm trying to add an action that takes no parameters I'm getting an InvalidOperatioNException that is saying: System.Action is not a GenericTypeDefinition. MakeGenericType may only be called on a type for which Type.IsGenericTypeDefinition is true. – Gilbert Williams Oct 16 '16 at 08:36
  • @GilbertWilliams Oh indeed, you need a special case when there's 0 parameters. Edited. – Kevin Gosse Oct 16 '16 at 08:38
  • Thank you so much! Works flawlessly. I'll try to figure out a much simpler way to do this. – Gilbert Williams Oct 16 '16 at 09:08