1

我需要能够从课堂外引发事件,所以我认为反射是解决方案。但是,在检索到事件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
    {
    }
}

我一定错过了一些东西,但我目前没有线索,希望能得到任何提示。

4

2 回答 2

3

好吧,首先,必须说这是一个非常糟糕的设计。事件调用是有意限制的。冒充一个对象并在未经其同意的情况下引发其事件正在打破对事件所有者和任何事件侦听器的各种假设。你永远不应该在生产代码中这样做。

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

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

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

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

但是......如果您选择忽略不这样做的所有非常充分的理由,那么您的代码存在问题:

ADelegate不仅仅是一个方法引用。它是一个方法引用调用该方法所需的上下文。

假设您有以下代码:

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

action.Methodwill be 引用Foo.Bar,while action.Targetwill be foo。在您的GetEventMethod功能中,您正在丢弃目标!委托知道将什么对象作为“ this”参数传递给方法,但您忽略了它。实际上,在您的情况下,由于您使用的是 lambda 表达式bar.TestEvent += (sender, args) => isRaised = true;,因此 ' this' 参数将是编译器生成的闭包对象。您希望引用该对象的唯一方法是通过Target委托上的对象。

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

当然,不要那样做,因为不要这样做。事件调用对于定义它们的类型是私有的(除非该类型选择公开它们)。如果不是这样,则该类型将仅公开一个Delegate-typed 属性。

于 2013-03-28T16:54:17.427 回答
1

我最近遇到了一种情况,我必须从派生类中引发一个事件,尽管在这里使用反射不是最好的设计,但它使我能够解决问题并在最后期限之前完成,所以我想我会把它传递下去。

下面的代码对工作代码进行了一些修改,但仍应作为在您自己的项目中实施类似解决方案的良好基础。

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

    }

}
于 2013-07-02T03:23:53.070 回答