引发反射事件会导致错误:“对象与目标类型不匹配”

时间:2013-03-28 16:22:44

标签: .net events reflection

我需要能够从课外引发事件,所以我认为Reflection是解决方案。但是,在检索到事件的MultiCastDelegate及其调用方法后,我无法调用它。当我这样做时,我得到一个TargetException,其中包含“对象与目标类型不匹配”的消息。

以下是重现问题的一些代码:

辅助类:

public static class Reflection
{
    public static MethodInfo GetEventMethod(this object obj, string eventName, out Type targetType)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        if (string.IsNullOrEmpty(eventName)) throw new ArgumentNullException("eventName");

        var mcDelegate = getEventMulticastDelegate(obj, eventName, out targetType);
        return mcDelegate != null ? mcDelegate.Method : null;
    }

    private static MulticastDelegate getEventMulticastDelegate(object obj, string eventName, out Type targetType)
    {
        // traverse inheritance tree looking for the specified event ...
        targetType = obj.GetType();
        while (true)
        {
            var fieldInfo = targetType.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);
            if (fieldInfo != null)
                return fieldInfo.GetValue(obj) as MulticastDelegate;

            if (targetType.BaseType == null)
                return null;

            targetType = targetType.BaseType;
        }
    }

    public static void RaiseEvent(this object obj, string eventName, params object[] parameters)
    {
        Type targetType;
        var methodInfo = obj.GetEventMethod(eventName, out targetType);
        if (methodInfo == null)
            throw new ArgumentOutOfRangeException("eventName", string.Format("Cannot raise event \"{0}\". Event is not supported by {1}", eventName, obj.GetType()));

        var targetObj = obj;
        if (targetType != obj.GetType())
            targetObj = obj.ConvertTo(targetType);

        methodInfo.Invoke(targetObj, parameters);  // *** ERROR HERE ***
    }
}

还有一些代码来测试它:

[TestClass]
public class Reflection
{
    [TestMethod]
    public void RaiseReflectedEvent()
    {
        var bar = new Bar();
        var isRaised = false;
        bar.TestEvent += (sender, args) => isRaised = true;
        bar.RaiseEvent("TestEvent", this, new EventArgs());
        Assert.IsTrue(isRaised);
        var foo = new Foo();
        isRaised = false;
        foo.TestEvent += (sender, args) => isRaised = true;
        foo.RaiseEvent("TestEvent", this, new EventArgs());
        Assert.IsTrue(isRaised);
    }

    private class Bar
    {
        public event EventHandler TestEvent;
    }

    private class Foo : Bar
    {
    }
}

我肯定错过了一些东西,但我现在已经没有线索了,并且会感激任何提示。

2 个答案:

答案 0 :(得分:3)

嗯,首先,必须说这是非常糟糕的设计。事件调用是有目的的。模仿对象并在未经其同意的情况下提升其事件会破坏事件所有者和任何事件监听器的各种假设。你永远不应该在生产代码中这样做。

其次,你所采取的方法一般不起作用。事件不必具有与其关联的字段,并且该字段不必与事件具有相同的名称。以下是有效的C#:

class Foo {
    public event EventHandler Bar { add { } remove { } }
}

(此表单通常用于将一个事件重定向到另一个事件)

第三,作为上述的推论,即使您找到了该字段,也可能不是调用该事件的正确方法。在某些语言(例如C ++ / CLI)中,事件可以与引发事件的Raise方法显式关联。不经过Raise方法调用代理可能是一个严重的逻辑错误。

然而 ...如果您选择忽略所有不这么做的强烈理由,这就是您的代码存在的问题:

Delegate不仅仅是方法参考。它是方法引用调用方法所需的上下文。

假设您有以下代码:

class Foo {
    void Bar() { }
    static void Main() {
        Foo foo = new Foo();
        Action action = new Action(foo.Bar);
    }
}

action.Method将引用Foo.Bar,而action.Target则为foo。在你的GetEventMethod函数中,你扔掉了目标!委托知道要将哪个对象作为“this”参数传递给方法,但是您忽略它。实际上,在您的情况下,由于您使用的是lambda表达式bar.TestEvent += (sender, args) => isRaised = true;,因此“this”参数将是编译器生成的闭包对象。您希望引用该对象的唯一方法是通过委托上的Target对象。

所以问题是你正在尝试重新实现Delegate类,但你也没有这样做。只需在委托实例本身上调用DynamicInvoke

除了当然,不要那样做,因为不要做这些。事件调用对于定义它们的类型是私有的(除非该类型选择公开它们)。如果不是这样的话,那么类型就会暴露出Delegate类型的属性。

答案 1 :(得分:1)

我最近遇到过一种情况,我不得不从派生类中引发一个事件,即使在这里使用Reflection不是最好的设计,它使我能够解决问题并达到截止日期,所以我想我会通过它沿。

下面的代码稍微从工作代码中修改,但仍应作为在您自己的项目中实施类似解决方案的良好基础。

protected void RaiseReflectedEvent( string eventName, params object[] args )
{

    var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    var eventField =
        this.GetType()
        .GetField( eventName, bindingFlags );

    if( eventField != null )
    {

        var eventDelegate = eventField.GetValue( this );
        if( eventDelegate != null )
        {
            ( (Delegate)eventDelegate ).DynamicInvoke( args );
        }

    }

}