2009-06-01 34 views

回答

120

明确实现的情况下,检查调用列表。您还需要检查null:

using System.Linq; // Required for the .Contains call below: 

... 

private EventHandler foo; 
public event EventHandler Foo 
{ 
    add 
    { 
     if (foo == null || !foo.GetInvocationList().Contains(value)) 
     { 
      foo += value; 
     } 
    } 
    remove 
    { 
     foo -= value; 
    } 
} 

使用上面的代码,如果调用订阅事件多次,它只会被忽略。

+13

您需要使用System.Linq。 – 2009-06-18 17:55:51

+0

只是为了澄清赫尔曼的评论;您必须通过将'使用System.Linq'添加到您的类或当前名称空间来包含命名空间'System.Linq'。 – 2010-08-13 11:34:11

+0

迷人。 LINQ对我来说仍然是新的,我必须查看它,并提醒它它意味着Language Integrated Query ...,然后想知道与EventHandlers及其InvocationList有什么关系? – fortboise 2011-11-11 20:54:07

12

您需要实现的增加和对事件remove访问,然后检查委托的目标列表,或者目标存储在名单。

在add方法,您可以使用Delegate.GetInvocationList方法来获取已添加到代表目标的清单。

因为如果代表被定义为在相同目标对象上链接到相同方法时比较相等,则可以运行该列表并进行比较,如果找不到比较相等的方法,则添加新代码。

这里的示例代码,编译为控制台应用程序:

using System; 
using System.Linq; 

namespace DemoApp 
{ 
    public class TestClass 
    { 
     private EventHandler _Test; 

     public event EventHandler Test 
     { 
      add 
      { 
       if (_Test == null || !_Test.GetInvocationList().Contains(value)) 
        _Test += value; 
      } 

      remove 
      { 
       _Test -= value; 
      } 
     } 

     public void OnTest() 
     { 
      if (_Test != null) 
       _Test(this, EventArgs.Empty); 
     } 
    } 

    class Program 
    { 
     static void Main() 
     { 
      TestClass tc = new TestClass(); 
      tc.Test += tc_Test; 
      tc.Test += tc_Test; 
      tc.OnTest(); 
      Console.In.ReadLine(); 
     } 

     static void tc_Test(object sender, EventArgs e) 
     { 
      Console.Out.WriteLine("tc_Test called"); 
     } 
    } 
} 

输出:

tc_Test called 

(即只有一次。)

+0

请忽略我的评论,我忘记了Linq使用。 – 2009-06-18 17:55:11

+0

最干净的解决方案(尽管不是最短的)。 – Shimmy 2017-09-19 07:14:41

0

有你单身的对象检查它的它会通知谁名单如果重复只能调用一次。或者,如果可能拒绝事件附件请求。

17

你真的应该在水槽的水平,而不是源代码级的处理这个问题。也就是说,不要在事件源处规定事件处理程序逻辑 - 将其留给处理程序(接收器)本身。

作为服务的开发者,谁是你说汇只能注册一次?如果他们因某种原因想要注册两次会怎样?如果您试图通过修改源来纠正汇点中的错误,那么在汇点级别纠正这些问题也是一个很好的理由。

我敢肯定,你有你的理由;一个事件源非法重复汇是不可估量的。但是也许你应该考虑一个可以保持事件语义完整的备用架构。

140

如何只用-=先删除事件,如果没有发现这是一个异常没有抛出

/// -= Removes the event if it has been already added, this prevents multiple firing of the event 
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii); 
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii); 
6

微软的Reactive Extensions (Rx) framework也可以用来做“订阅只有一次”。

给出一个鼠标事件foo.Clicked,这里是如何订阅和接收只有一个调用:

Observable.FromEvent<MouseEventArgs>(foo, "Clicked") 
    .Take(1) 
    .Subscribe(MyHandler); 

... 

private void MyHandler(IEvent<MouseEventArgs> eventInfo) 
{ 
    // This will be called just once! 
    var sender = eventInfo.Sender; 
    var args = eventInfo.EventArgs; 
} 

除了提供“订阅一次”功能,在RX方法提供撰写一起事件的能力或过滤事件。这非常漂亮。

0

在silverlight中你需要说e.Handled = true;在事件代码中。

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
    e.Handled = true; //this fixes the double event fire problem. 
    string name = (e.OriginalSource as Image).Tag.ToString(); 
    DoSomething(name); 
} 

如果有帮助,请打勾我。

1

创建一个Action而不是一个事件。你的类可能看起来像:

public class MyClass 
{ 
       // sender arguments  <-----  Use this action instead of an event 
    public Action<object, EventArgs> OnSomeEventOccured; 

    public void SomeMethod() 
    { 
      if(OnSomeEventOccured!=null) 
       OnSomeEventOccured(this, null); 
    } 

} 
20

我测试过的每解决方案,最好的一个(考虑性能)是:

private EventHandler _foo; 
public event EventHandler Foo { 

    add { 
     _foo -= value; 
     _foo += value; 
    } 
    remove { 
     _foo -= value; 
    } 
} 

没有使用Linq的需要。在取消订阅之前不需要检查null(有关详细信息,请参阅MS EventHandler)。无需记得到处取消订阅。

相关问题