Очень полезно! Спасибо! Я хотел немного больше контролировать события для своего проекта, поэтому я адаптировал ответ @ Matthias для отправки настраиваемого события «Значение без изменений». Я положил пример на GitHub .
Я также включил исправление @ Grzegorz, чтобы оно работало правильно, если пользователь тащит палец за сегментированный элемент управления.
Явно реализуйте событие и проверьте список вызовов. Вам также необходимо проверить наличие 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;
}
}
Используя приведенный выше код, если вызывающий абонент подписывается на событие несколько раз, оно будет просто проигнорировано.
Вам необходимо реализовать средства доступа добавления и удаления для события, а затем проверить целевой список делегата или сохранить целевые объекты в списке.
В методе добавления вы можно использовать метод 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
(т.е. только один раз)
пусть ваш одноэлементный объект проверяет список того, кого он уведомляет, и вызывает только один раз, если дублируется. В качестве альтернативы, если возможно, отклоните запрос на вложение события.
Вы действительно должны обрабатывать это на уровне приемника, а не на уровне источника. То есть не предписывайте логику обработчика событий в источнике события - оставьте это самим обработчикам (приемникам).
Как разработчик службы, кто вы такие, чтобы говорить, что приемники могут регистрироваться только один раз? Что, если они по какой-то причине захотят зарегистрироваться дважды? И если вы пытаетесь исправить ошибки в стоках, изменяя источник, это снова хорошая причина для исправления этих проблем на уровне приемников.
Я уверен, что у вас есть свои причины; источник событий, для которого дублирование приемников недопустимо, не является непостижимым. Но, возможно, вам следует рассмотреть альтернативную архитектуру, которая не затрагивает семантику события.
Как насчет того, чтобы сначала удалить событие с помощью - =
, если оно не найдено, исключение не генерируется
/// -= 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);
Фреймворк 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 предлагает возможность объединять события вместе или фильтровать события. Это довольно здорово.