Как обеспечить, чтобы событие было подписано только один раз

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

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

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

Как мне пойти на внедрение такого охранника?

Ответ 1

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

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);
}

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

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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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;

Подробнее см. Внедрение Postharp EventInterceptionAspect для предотвращения дважды обработанного обработчика событий

Ответ 5

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

class MemberClass
{
        private EventHandler _event;

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

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

Ответ 6

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

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

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