С#, чтобы предотвратить перехват обработчика события дважды
Дубликат: Как обеспечить, чтобы событие было подписано только один раз
и Добавлен ли обработчик событий?
У меня есть singleton, который предоставляет некоторую услугу, а мои классы подключаются к некоторым событиям на нем, иногда класс дважды подключается к событию, а затем дважды вызывается.
Я ищу классический способ предотвратить это. почему-то мне нужно проверить, не подключен ли я к этому событию...
Ответы
Ответ 1
Явно реализовать событие и проверить список вызовов. Вам также необходимо проверить значение 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;
}
}
Используя вышеприведенный код, если вызывающий абонент несколько раз присоединяется к событию, он просто будет проигнорирован.
Ответ 2
Как просто удалить событие сначала с помощью -=
, если он не найден, исключение не выбрано
/// -= 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);
Ответ 3
Я тестировал каждое решение, и лучший (учитывая производительность):
private EventHandler _foo;
public event EventHandler Foo {
add {
_foo -= value;
_foo += value;
}
remove {
_foo -= value;
}
}
Без использования Linq. Нет необходимости проверять значение null перед отменой подписки (подробнее см. MS EventHandler). Не нужно забывать, чтобы делать запретную подписку повсюду.
Ответ 4
Вы действительно должны справиться с этим на уровне раковины, а не на уровне источника. То есть, не предписывайте логику обработчика событий в источнике события - оставьте это для самих обработчиков (стоков).
Как разработчик службы, кто вы скажете, что приемники могут регистрироваться только один раз? Что, если они захотят зарегистрироваться дважды по какой-то причине? И если вы пытаетесь исправить ошибки в приемниках, изменив исходный код, это снова является хорошей причиной для исправления этих проблем на уровне приемника.
Я уверен, что у вас есть свои причины; источник событий, для которых дублирующие раковины являются незаконными, не является непостижимым. Но, возможно, вам стоит рассмотреть альтернативную архитектуру, которая оставляет семантику события неповрежденным.
Ответ 5
Вам необходимо реализовать добавление и удаление аксессуаров в событии, а затем проверить целевой список делегата или сохранить целевые объекты в списке.
В методе добавления вы можете использовать метод 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
(т.е. только один раз)
Ответ 6
Microsoft 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 предлагает возможность составлять события вместе или фильтровать события. Это довольно изящно.
Ответ 7
Создайте действие вместо события. Ваш класс может выглядеть так:
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);
}
}
Ответ 8
у вас есть объект singleton, проверьте его список того, кто он уведомляет, и только один раз вызывается, если его дублируют. Альтернативно, если возможно, отклоните запрос на вступление в событие.
Ответ 9
Может быть, мой ответ на подобный пост поможет:
Дифференциация между событиями, вызванными взаимодействием с пользователем и моим собственным кодом
Ответ 10
В 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);
}
Пожалуйста, отметьте меня, если это поможет.