6

我最近了解了如何使用 C# 扩展方法来更轻松地调用事件,并且我越来越多地使用它们。我最近遇到了一个我不明白的奇怪问题,我想知道是否有人可以解释一下。

尝试将事件处理程序扩展方法设置为另一个事件的事件处理程序时会出现此问题。这是我正在做的一个例子:

public static class EventHandlerExtensions
{
    public static void Raise<TEventArgs>(
        this EventHandler<TEventArgs> eventHandler, 
        object sender, TEventArgs args)  where TEventArgs:EventArgs
    {
        if (eventHandler != null)
        {
            eventHandler(sender, args);
        }
    }
}

public class Test
{
    private event EventHandler<EventArgs> EventA;
    private event EventHandler<EventArgs> EventB;

    public Test()
    {
        Console.WriteLine("::Start");
        EventB += EventA.Raise;
        EventA += (s, a) => Console.WriteLine("Event A raised");
        EventB.Raise(this, EventArgs.Empty);
        Console.WriteLine("::End");
    }
}

在此示例中,应触发 EventA 作为触发 EventB 的结果。但是,当我运行此代码时,EventB 会触发,但 A 上的扩展方法没有找到任何侦听器。

如果我改变顺序,一切正常:

Console.WriteLine("::Start");
EventA += (s, a) => Console.WriteLine("Event A raised");
EventB += EventA.Raise;
EventB.Raise(this, EventArgs.Empty);
Console.WriteLine("::End");

此外,从 lambda 调用 EventA.Raise 可以正常工作:

Console.WriteLine("::Start");
EventB += (s, a) => EventA.Raise(s, a);
EventA += (s, a) => Console.WriteLine("Event A raised");
EventB.Raise(this, EventArgs.Empty);
Console.WriteLine("::End");

这只是一个简单的示例,但我正在尝试创建一个类,该类可以以最简洁的方式重新调度添加到其中的事件源的事件。我不想创建命名方法只是为了重新分派相同的事件,我也不想存储以后可以从事件处理程序中取消挂钩的 lambda 函数列表。大多数情况下,我只是好奇为什么会这样?

有任何想法吗?

4

2 回答 2

4

您可以通过 Raise 函数将 EventA 的旧值捕获到闭包中。因为后来你使用 += 它改变了 EventA 的值,但你的闭包仍然有一个旧值。

你编码:

EventB += EventA.Raise;
EventA += (s, a) => Console.WriteLine("Event A raised"); 

可以扩展为等效代码,这清楚地说明了为什么你会得到旧的委托:

var oldEventA = EventA;
EventB += oldEventA.Raise; // captures old value here
// now EventA changed to new value 
EventA = oldEventA + ((s, a) => Console.WriteLine("Event A raised");)

您可以在之前添加以下内容以EventB += EventA.Raise验证代码是否确实为 A 引发了旧事件:

EventA += (s, a) => Console.WriteLine("Old Event A raised");
于 2012-05-24T01:54:44.970 回答
3

委托对象是不可变的。很像弦乐。因此,当您分配 EventA 时,您会创建一个对象。EventB 仍然以旧的为目标,即尚未分配任何事件处理程序的那个。您必须交换这两个语句才能解决问题。

于 2012-05-24T02:16:07.580 回答