如何取消绑定事件处理程序的所有实例?为什么不 - =删除所有实例?

时间:2012-07-20 20:24:47

标签: c# jquery events

请考虑以下代码段:

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!
---

5 个答案:

答案 0 :(得分: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

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

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

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

答案 1 :(得分:1)

尝试此解决方案Removing Event Handlers using Reflection

 Delegate[] dellist = myEvent.GetInvocationList();
    foreach (Delegate d in v)
           myEvent-= (d as MyDelegate);//MyDelegate is type of delegate

答案 2 :(得分: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类将被其他人使用,我不会推荐上述代码,因为它的行为与其他类不同。

答案 3 :(得分: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();
    }
}

答案 4 :(得分:-2)

Event = Delegate.RemoveAll(Event, handler);

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

相关问题