GTHvidsten GTHvidsten - 1 month ago 9
C# Question

Converting instance of Interface with generic method to Action

I'm struggling to create instances of

Action<>
using the
MethodInfo
's I can retrieve using reflection.

Using the example below I can easily call a custom method on my objects, but I would really like to convert them to Actions.

I have tried doing it by using
Delegate.CreateDelegate
but I can't seem to get this to return an Action with a generic type.

Example

I have created the following interface:

interface IMyTest { } // Marker interface
interface<TInput> IMyTest : IMyTest {
void MyMethod(TInput input);
}


Then I inherit the interface in a class:

class MyClass : IMyTest<DateTime>, IMyTest<int> {
void MyMethod(DateTime input) {
// Do something
}
void MyMethod(int input) {
// Do something else
}
}


And then I create a method that uses reflection to find and call "MyMethod" from the interface instance:

void DoCallMyMethod(object target, object input) {
IEnumerable<Type> interfaces = target.GetType().GetInterfaces()
.Where(x => typeof(IMyTest).IsAssignableFrom(x) && x.IsGenericType);

foreach (Type @interface in interfaces) {
Type type = @interface.GetGenericArguments()[0];
MethodInfo method = @interface.GetMethod("MyMethod", new Type[] { type });

if (method != null) {
method.Invoke(target, new[] { input });
}
}
}


And finally I put it all together:

MyClass foo = new MyClass();
DoCallMyMethod(foo, DateTime.Now);
DoCallMyMethod(foo, 47);


What I would want

Inside
DoCallMyMethod
I would like
MethodInfo method
to be converted to a generic
Action
so that the result would be something like this:

Action<type> myAction = method;


But this obviously doesn't work.

I found some similar SO posts (but none that cover exactly my case) that ended up with an answer similar to this:

Action<object> action =
(Action<object>)Delegate.CreateDelegate(typeof(Action<object>), target, method);


But this doesn't work because "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."

How can I get an Action with the specified
type
as its input type, while still retaining the object references (without new instances appearing)?

Answer

You can't create an Action<object> from one of MyMethods because the method is expecting a specific object's type (int, DateTime) and if you treat it as an Action<object> you might invoke it with objects of any type.

Since what you need is return a Action<T> there is not need to pass the input value to DoCallMyMethod method. You can pass the input type as a generic parameter:

public Action<T> DoCallMyMethod<T>(object target)
{
    var @interface = typeof(IMyTest<T>);
    if (@interface.IsAssignableFrom(target.GetType()))
    {
        var method = @interface.GetMethod("MyMethod");
        if (method != null)
        {
            var action = Delegate.CreateDelegate(typeof(Action<T>), target, method) as Action<T>;                    
            return action;
        }
    }

    return null;
}

Then, use it like this:

MyClass foo = new MyClass();
var action1 = DoCallMyMethod<DateTime>(foo);
var action2 = DoCallMyMethod<int>(foo);

action1(DateTime.Now);
action2(47);

If you don't know at compile time the type of the input you can try this:

public Delegate DoCallMyMethod(object target, Type inputType)
{
    var @interface = typeof(IMyTest<>).MakeGenericType(inputType);
    if (@interface.IsAssignableFrom(target.GetType()))
    {
        var method = @interface.GetMethod("MyMethod");
        if (method != null)
        {
            var @delegate = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(inputType), target, method);
            return @delegate;
        }
    }

    return null;
}

And use it like so:

MyClass foo = new MyClass();
var action1 = DoCallMyMethod(foo, typeof(DateTime)) as Action<DateTime>;
var action2 = DoCallMyMethod(foo, typeof(int)) as Action<int>;

action1(DateTime.Now);
action2(47);

//or

int input = 47;
var @delegate = DoCallMyMethod(foo, input.GetType());
@delegate.DynamicInvoke(input);

But if you need to returns an Action<object> you can make an action receiving an object and calling the method if the object's type is valid:

public Action<object> DoCallMyMethod(object target, Type inputType)
{
    var @interface = typeof(IMyTest<>).MakeGenericType(inputType);
    if (@interface.IsAssignableFrom(target.GetType()))
    {
        var method = @interface.GetMethod("MyMethod");
        if (method != null)
        {
            Action<object> action = obj =>
            {
                if (obj.GetType().IsAssignableFrom(inputType))
                    method.Invoke(target, new object[] { obj });
            };

            return action;
        }
    }

    return null;
}

And...

MyClass foo = new MyClass();
Action<object> action = DoCallMyMethod(foo, typeof(int));
action(47);         // MyMethod(int) is called.
action("...");      // Nothing happens.

Now you have an Action<object> that calls MyMethod(int) only when an integer is passed.