Шаблон C# для предотвращения обработчика событий, сцепленного дважды, [копирует]

Очень полезно! Спасибо! Я хотел немного больше контролировать события для своего проекта, поэтому я адаптировал ответ @ Matthias для отправки настраиваемого события «Значение без изменений». Я положил пример на GitHub .

Я также включил исправление @ Grzegorz, чтобы оно работало правильно, если пользователь тащит палец за сегментированный элемент управления.

93
задан Community 23 May 2017 в 12:26
поделиться

6 ответов

Явно реализуйте событие и проверьте список вызовов. Вам также необходимо проверить наличие 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;
    }
}

Используя приведенный выше код, если вызывающий абонент подписывается на событие несколько раз, оно будет просто проигнорировано.

144
ответ дан 24 November 2019 в 06:10
поделиться

Вам необходимо реализовать средства доступа добавления и удаления для события, а затем проверить целевой список делегата или сохранить целевые объекты в списке.

В методе добавления вы можно использовать метод 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

(т.е. только один раз)

14
ответ дан 24 November 2019 в 06:10
поделиться

пусть ваш одноэлементный объект проверяет список того, кого он уведомляет, и вызывает только один раз, если дублируется. В качестве альтернативы, если возможно, отклоните запрос на вложение события.

0
ответ дан 24 November 2019 в 06:10
поделиться

Вы действительно должны обрабатывать это на уровне приемника, а не на уровне источника. То есть не предписывайте логику обработчика событий в источнике события - оставьте это самим обработчикам (приемникам).

Как разработчик службы, кто вы такие, чтобы говорить, что приемники могут регистрироваться только один раз? Что, если они по какой-то причине захотят зарегистрироваться дважды? И если вы пытаетесь исправить ошибки в стоках, изменяя источник, это снова хорошая причина для исправления этих проблем на уровне приемников.

Я уверен, что у вас есть свои причины; источник событий, для которого дублирование приемников недопустимо, не является непостижимым. Но, возможно, вам следует рассмотреть альтернативную архитектуру, которая не затрагивает семантику события.

19
ответ дан 24 November 2019 в 06:10
поделиться

Как насчет того, чтобы сначала удалить событие с помощью - = , если оно не найдено, исключение не генерируется

/// -= 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);
165
ответ дан 24 November 2019 в 06:10
поделиться

Фреймворк Microsoft Reactive Extensions (Rx) также можно использовать для «подписки только один раз».

Учитывая событие мыши foo.Clicked, вот как подписаться и получить только один вызов:

Observable.FromEvent<MouseEventArgs>(foo, nameof(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 предлагает возможность объединять события вместе или фильтровать события. Это довольно здорово.

5
ответ дан 24 November 2019 в 06:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: