【问题标题】:Converting instance of Interface with generic method to Action将具有泛型方法的接口实例转换为操作
【发布时间】:2016-11-04 16:58:18
【问题描述】:

我正在努力使用可以使用反射检索的MethodInfo 创建Action<> 的实例。

使用下面的示例,我可以轻松地在我的对象上调用自定义方法,但我真的很想将它们转换为动作。

我已经尝试使用 Delegate.CreateDelegate 来实现它,但我似乎无法让它返回一个泛型类型的 Action。

示例

我已经创建了如下界面:

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

然后我在一个类中继承接口:

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

然后我创建一个使用反射从接口实例中查找并调用“MyMethod”的方法:

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 });
        }
    }
}

最后我把它们放在一起:

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

我想要什么

DoCallMyMethod 内部,我希望将MethodInfo method 转换为通用Action,以便结果如下所示:

Action<type> myAction = method;

但这显然行不通。

我发现了一些类似的 SO 帖子(但没有一个完全涵盖我的情况),最终得到的答案类似于:

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

但这不起作用,因为“无法绑定到目标方法,因为它的签名或安全透明度与委托类型的不兼容。”

我怎样才能得到一个以指定type 作为输入类型的动作,同时仍然保留对象引用(不出现新实例)?

【问题讨论】:

    标签: c# generics reflection delegates action


    【解决方案1】:

    您不能从MyMethods 之一创建Action&lt;object&gt;,因为该方法需要特定对象的类型(intDateTime),如果您将其视为Action&lt;object&gt;,您可能会调用它与任何类型的对象。

    由于您需要返回一个Action&lt;T&gt;,因此无需将输入值传递给DoCallMyMethod 方法。您可以将输入类型作为泛型参数传递:

    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;
    }
    

    然后,像这样使用它:

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

    如果你在编译时不知道输入的类型,你可以试试这个:

    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;
    }
    

    然后像这样使用它:

    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);
    

    但是如果你需要返回一个Action&lt;object&gt;,你可以做一个动作来接收一个对象并在对象的类型有效的情况下调用该方法:

    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;
    }
    

    还有……

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

    现在您有一个Action&lt;object&gt;,它仅在传递整数时调用MyMethod(int)

    【讨论】:

    • 它以某种方式起作用,但是我还需要一个步骤,我无法从您的答案中推断出来(并且可能从问题中不清楚)。一旦我在不知道输入类型的情况下获得delegate,我怎样才能从中获得通用的 Action ?例如,如果我也有一个方法AddExternalCaller&lt;T&gt;(Action&lt;T&gt; callback),那么callback 可能是MyMethod 的变体之一。然后如何让AddExternalCaller()DoCallMyMethod()List&lt;Action&lt;object&gt;&gt; 的全局集合添加操作?
    • @GTHvidsten:但是您不能将Action&lt;T&gt;Action&lt;int&gt;Action&lt;DateTime&gt; 添加到List&lt;Action&lt;object&gt;&gt;,因为Action&lt;int&gt; 不是Action&lt;object&gt;。当期望int 时,您不能调用传递对象的方法。事实上,这在另一个方向上是有效的,因为Action&lt;T&gt; 是逆变的,您可以将Action&lt;object&gt; 分配给Action&lt;int&gt; 变量,您可以调用一个期望具有整数对象的方法。我认为您需要更改保存回调的设计。
    • 我之前从Action&lt;T&gt; 转换为Action&lt;object&gt; 的一种方法是使用以下语法进行转换:void Convert&lt;T&gt;(Action&lt;T&gt; callback) { Action&lt;object&gt; foo = input =&gt; callback((T)input); }。我能够像这样将动态委托转换为 ActionAction&lt;object&gt; handler = request =&gt; @delegate.DynamicInvoke(request);。截至目前,它也适用于我的场景。你觉得这个有什么特别的缺点吗?
    • @GTHvidsten 好吧,如果用错误的类型调用这些代表中的任何一个,就会抛出异常。如果您存储 Action&lt;int&gt; 并使用 string 对象调用它。
    • 我将添加一些额外的后端以确保我只检索具有正确类型的 Action&lt;&gt; 实例,所以我应该被覆盖...我希望 :) 感谢您的回答!跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 2010-12-20
    • 2016-10-20
    • 1970-01-01
    相关资源
    最近更新 更多