Использую ли я правильный подход для мониторинга задач, которые я хочу выполнять при создании дескриптора? - PullRequest
6 голосов
/ 12 октября 2009

Существует ли общепринятая лучшая практика для создания обработчика событий, который сам отписывается?

Например, первое, что я придумал, это что-то вроде:

// Foo.cs

// ...
Bar bar = new Bar(/* add'l req'd state */);
EventHandler handler = new EventHandler(bar.HandlerMethod);

bar.HandlerToUnsubscribe = handler;

eventSource.EventName += handler;
// ...

// Bar.cs

class Bar
{
    /* add'l req'd state */

    // .ctor

    public EventHandler HandlerToUnsubscribe { get; set; }

    public void HandlerMethod(object sender, EventArgs args)
    {
        // Do what must be done w/ add'l req'd state
        ((EventSourceType)sender).EventName -= this.HandlerToUnsubscribe;
    }
}

Сказать, что это кажется хакерским / плохим, - преуменьшение. Он тесно связан с временной зависимостью (HandlerToUnsubscribe должно быть назначено точное значение в нужное время). Я чувствую, что в этом случае я играю роль соучастника - что-то глупое или простое, что я упускаю?

Контекст:

Я создаю привязку между пользовательским интерфейсом и собственной инфраструктурой команд в Winforms (используя полезную ICommand в System.Windows.Input). Один из аспектов инфраструктуры привязки заключается в том, что пользователи, которые создают привязку между компонентом команды пользовательского интерфейса (например, кнопкой на панели инструментов или элементом меню), имеют возможность прослушивать событие CanExecuteChanged команды, а затем обновляют состояние пользовательского интерфейса на основе этого: обычно для свойства Enabled установлено значение true или false.

Техника в целом работает довольно хорошо, но есть способы для запуска события до того, как будет создан дескриптор компонента пользовательского интерфейса. Я пытаюсь гарантировать, что предоставленный обработчик не будет запущен, если дескриптор не был создан. В результате я рассматриваю возможность предоставления общего вспомогательного класса ("Bar"), который поможет реализации. Цель Bar - проверить, существует ли соответствующий дескриптор. Если так, отлично! Если нет, то он подпишется на соответствующее событие IsHandleCreated, чтобы поставляемые обработчики запускались при создании дескриптора. (Это важно, потому что клиент может установить свои привязки в .ctor пользовательского интерфейса до того, как дескриптор существует.) Однако я хочу, чтобы эта подписка была полностью прозрачной, и поэтому я также хочу, чтобы каждый обработчик событий автоматически отписывался от IsHandleCreated как только он закончит работу.

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

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

Ответы [ 8 ]

11 голосов
/ 12 октября 2009

Грег

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

Достаточно просто создать собственную очередь сообщений с нуля, используя Queue{Action{object}}, где объекты ставятся в очередь сами, и вы просто удаляете элементы из очереди по мере их вызова.

1 голос
/ 12 октября 2009

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

1) Возможно, вы захотите снова подключить метод позже и снова запустить его. Хотя я полагаю, вы можете сбросить свой bool

2) У вас все еще есть эта ссылка, которая может в конечном итоге сохранить ваш класс в памяти вместо того, чтобы собирать мусор.

Одним из вариантов является использование анонимного метода и замыкания при определении обработки события:

public class Foo
{
    public EventHandler<EventArgs> MyEvent;

    public void FireEvent()
    {
        if(MyEvent != null)
            MyEvent(this, EventArgs.Empty);
    }
}

Foo obj = new Foo();

Action<object, EventArgs> action = new Action<object, EventArgs>((sender, args) =>
{
    // We're now running the event handler, so unsubscribe
    obj.MyEvent -= new EventHandler<EventArgs>(action);

    // Do whatever you wanted to do when the event fired.
});

obj.MyEvent += new EventHandler<EventArgs>(action);
1 голос
/ 12 октября 2009

как насчет обработчика, который запускается только один раз? Примерно так:


if (wasRun)
    return;
wasRun = true;

1 голос
/ 12 октября 2009

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

bool runMyEvent = true;

void Handler(object sender, EventArgs e) {
   if (runMyEvent) {
      // handler here
      runMyEvent = false;
   } else {
      return;
   }
}
0 голосов
/ 12 октября 2009

Обычно я делаю что-то вроде следующего, чтобы реализовать обработчик событий 1 раз.

void OnControlClickOneTime(this Control c, EventHandler e) {
  EventHandler e2 = null;
  e2 = (s,args) => {
    c.Click -= e2;
    e(s,args);
  };
  c.Click += e2;
}
0 голосов
/ 12 октября 2009

Существует подробная статья о CodeProject: Слабые события , в которой рассматривается несколько решений этой проблемы.

0 голосов
/ 12 октября 2009

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

Основная идея заключается в том, что, когда подписчики собираются GC, ваш обработчик заметит это и выбросит их из списка подписчиков.

0 голосов
/ 12 октября 2009

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

Я тоже не знаю, хорошая ли это идея, просто еще одна мысль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...