С#, чтобы предотвратить перехват обработчика события дважды

Дубликат: Как обеспечить, чтобы событие было подписано только один раз и Добавлен ли обработчик событий?

У меня есть 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, проверьте его список того, кто он уведомляет, и только один раз вызывается, если его дублируют. Альтернативно, если возможно, отклоните запрос на вступление в событие.

Ответ 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);
}

Пожалуйста, отметьте меня, если это поможет.