-1

考虑这个片段:

class Foo
{
    public event Action Event;
    public void TriggerEvent()
    {
        if (Event != null) {
            Event();
        }
    }
}

static void Handler()
{
    Console.WriteLine("hi!");
}

static void Main()
{
    var obj = new Foo();
    obj.Event += Handler;
    obj.Event += Handler;
    obj.TriggerEvent();
    Console.WriteLine("---");
    obj.Event -= Handler;
    obj.TriggerEvent();
}

我得到的输出:

hi!
hi!
---
hi!

最后一个“嗨!” 出乎意料。要删除它,我必须再打电话Event -= Handler;一次。但是如果我不知道处理程序被绑定了多少次怎么办?

更新:知道这有点违反直觉的行为背后的原因会很有趣:为什么-=删除所有实例?

更新 2:我意识到由于与 jQuery 的不同,我发现这种行为违反直觉。

var handler = function() { console.log('hi!'); }, obj = {};
$(obj).on("event", handler).on("event", handler).trigger("event");
console.log("---");
$(obj).off("event", handler).trigger("event");

输出:

hi!
hi!
---
4

5 回答 5

2

我想我理解为什么你可能认为你的例子是反直觉的。

考虑这个修改

var del = new Action(Handler);
obj.Event += del;
obj.Event += del;
obj.TriggerEvent();
Console.WriteLine("---");
obj.Event -= del;
obj.TriggerEvent();

它的工作原理与您的完全相同,但为什么呢?

当你使用

obj.Event += Handler

编译器在你背后做了一些事情。它创建了Action(Handler)三次(两次添加,一次删除)的新实例。在修改中,我们使用完全相同的委托对象。

所以真正的问题是:在你的例子中,为什么删除甚至起作用?您正在传递一个要删除的对象,该对象不用于添加。答案是代表具有价值平等。

var del1 = new Action(Handler);
var del2 = new Action(Handler);
Console.WriteLine("Reference equal? {0}, Value equal? {1}", Object.ReferenceEquals(del1, del2), del1.Equals(del2));
// Reference equal? False, Value equal? True

所以现在你可能会想,“为什么要添加两个事件处理程序?不应该只有一个,因为它们是同一个处理程序吗?”

答案是不”。多播委托不关心您是否多次添加相同的处理程序,它不是一个集合,它是一个列表。

当您删除一个处理程序时,它会识别出其列表中有两个相同的处理程序并删除了其中一个。

于 2012-07-20T21:39:55.460 回答
1

试试这个解决方案使用反射删除事件处理程序

或者

 Delegate[] dellist = myEvent.GetInvocationList();
    foreach (Delegate d in v)
           myEvent-= (d as MyDelegate);//MyDelegate is type of delegate
于 2012-07-20T20:35:48.947 回答
1

委托组合您分配给它的所有处理程序。如果您两次分配相同的处理程序,它将被调用两次并且必须被删除两次。我不认为这是违反直觉的。

如果您可以控制定义事件的类,则可以使用类似以下的内容一次删除特定处理程序的所有实例:

private Action _Event;

public event Action Event
{
    add
    {
        _Event += value;
    }
    remove
    {
        while (_Event != null && _Event.GetInvocationList().Contains(value))
        {
            _Event -= value;
        }
    }
}

如果您无法控制事件,那么您必须接受-=操作员仅删除处理程序的一个实例。这是语言设计的,无法更改。

这就像List<string>多次添加相同的字符串。如果要删除该字符串的所有实例,则必须多次调用 Remove 方法。

Foo如果您的类将被其他人使用,我不会推荐上面的代码,因为它的行为与任何其他类都不同。

于 2012-07-20T23:07:17.197 回答
0

委托应该在事件中“包装”,例如将实例字段包装在属性中。然后你可以控制它们。

public class Test
{
    public class Foo
    {
        private Action _event;
        public event Action Event
        {
            add { _event += value; }
            remove { _event -= value; }
        }

        public void DoEvent()
        {
            if (_event != null)
                _event ();
        }

        public void ClearEvent()
        {
            _event = null;
        }
    }

    static void Handler() {
        Console.WriteLine("hi!");
    }

    static void Main()
    {
        var foo = new Foo();

        foo.Event += Handler;
        foo.Event += Handler;
        foo.DoEvent();
        Console.WriteLine("---");

        foo.ClearEvent();

        foo.DoEvent();

        Console.Read();
    }
}
于 2012-07-20T20:35:03.100 回答
-2
Event = Delegate.RemoveAll(Event, handler);

请注意,这不是线程安全的,它只能在声明事件的类中工作。

于 2012-07-20T20:27:18.893 回答