Как обеспечить подписку на событие только один раз - PullRequest
42 голосов
/ 15 декабря 2008

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

Например, я бы хотел сделать следующее:

if (*not already subscribed*)
{
    member.Event += new MemeberClass.Delegate(handler);
}

Как бы я внедрил такую ​​охрану?

Ответы [ 6 ]

57 голосов
/ 15 августа 2011

Я добавляю это во все повторяющиеся вопросы, только для записи. Этот шаблон работал для меня:

myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;

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

35 голосов
/ 15 декабря 2008

Если вы говорите о событии в классе, к которому у вас есть доступ к источнику, вы можете поместить охрану в определение события.

private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;

public event EventHandler<MyDelegateType> MyEvent
{
   add 
   {
      if (_myEvent == null)
      {
         _myEvent += value;
      }
   }
   remove
   {
      _myEvent -= value;
   }
}

Это гарантирует, что только один подписчик может подписаться на событие в этом экземпляре класса, предоставляющего событие.

РЕДАКТИРОВАТЬ см. Комментарии о том, почему приведенный выше код является плохой идеей и не безопасен для потоков.

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

еще не подписан

с членом bool класса клиента, который устанавливается при первой подписке на событие.

Редактировать (после принятия): На основании комментария от @Glen T (отправитель вопроса) код для принятого решения, с которым он пошел, находится в классе клиента:

if (alreadySubscribedFlag)
{
    member.Event += new MemeberClass.Delegate(handler);
}

ГдеreadySubscribeedFlag - это переменная-член в клиентском классе, которая отслеживает первую подписку на конкретное событие. Люди, просматривающие здесь первый фрагмент кода, примите к сведению комментарий @Rune - не очень хорошая идея изменить поведение подписки на событие неочевидным способом.

РЕДАКТИРОВАТЬ 31/7/2009: Пожалуйста, смотрите комментарии @Sam Saffron. Как я уже говорил, и Сэм согласен, что первый метод, представленный здесь, не является разумным способом изменить поведение подписки на событие. Потребители класса должны знать о его внутренней реализации, чтобы понять его поведение. Не очень приятно.
@Sam Saffron также комментирует безопасность потоков. Я предполагаю, что он имеет в виду возможное состояние гонки, когда два подписчика (близкие к) одновременно пытаются подписаться, и они оба могут в конечном итоге подписаться. Замок может быть использован для улучшения этого. Если вы планируете изменить способ подписки на события, то я советую вам прочитать о том, как сделать подписку добавлением / удалением свойств безопасной для потока .

7 голосов
/ 15 декабря 2008

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

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

4 голосов
/ 20 апреля 2012

U может использовать Postsharper, чтобы написать один атрибут только один раз и использовать его в обычных событиях. Повторно используйте код. Пример кода приведен ниже.

[Serializable]
public class PreventEventHookedTwiceAttribute: EventInterceptionAspect
{
    private readonly object _lockObject = new object();
    readonly List<Delegate> _delegates = new List<Delegate>();

    public override void OnAddHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(!_delegates.Contains(args.Handler))
            {
                _delegates.Add(args.Handler);
                args.ProceedAddHandler();
            }
        }
    }

    public override void OnRemoveHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(_delegates.Contains(args.Handler))
            {
                _delegates.Remove(args.Handler);
                args.ProceedRemoveHandler();
            }
        }
    }
}

Просто используйте это так.

[PreventEventHookedTwice]
public static event Action<string> GoodEvent;

Подробнее см. Реализация Postsharp EventInterceptionAspect для предотвращения подключения обработчика событий дважды

3 голосов
/ 15 декабря 2008

Вам нужно будет либо сохранить отдельный флаг, указывающий, подписаны вы или нет, либо, если у вас есть контроль над MemberClass, предоставить реализации методов добавления и удаления для события:

class MemberClass
{
        private EventHandler _event;

        public event EventHandler Event
        {
            add
            {
                if( /* handler not already added */ )
                {
                    _event+= value;
                }
            }
            remove
            {
                _event-= value;
            }
        }
}

Чтобы решить, был ли добавлен обработчик, вам нужно сравнить делегаты, возвращенные из GetInvocationList (), по _event и по значению.

1 голос
/ 09 июня 2016

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

member.Event -= eventHandler;
member.Event += eventHandler;

Это делает бременем для разработчика, что является неправильным способом сделать это, но для быстрого и грязного это быстро и грязно.

...