Is there a generally-accepted best practice for creating an event handler that unsubscribes itself?
E.g., the first thing I came up with is something like:
// 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 r开发者_开发技巧eq'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;
}
}
To say that this feels hackish/bad is an understatement. It's tightly coupled with a temporal dependency (HandlerToUnsubscribe
must be assigned the exact value at the exact right time). I feel like I must be playing the part of a complicator in this case--- is there something stupid or simple that I'm missing?
Context:
I'm creating a binding between the UI and a proprietary commanding infrastructure in Winforms (using the useful ICommand
in System.Windows.Input). One aspect of the binding infrastructure is that users who create a binding between a UI command component (like a toolbar button or menu item) have the option to listen to the command's CanExecuteChanged
event and then update the UI's state based on that-- typically setting the Enabled property to true
or false
.
The technique generally works quite well, but there are ways for the event to be fired prior to a ui component's handle having been created. I'm trying to guarantee that the provided handler isn't run unless the handle has been created. Resultantly, I'm considering providing a general helper class ("Bar
") that will aid implementation. The goal of Bar
is to check to see if the appropriate handle exists. If so, great! If not, it will subscribe to appropriate IsHandleCreated
event so that the supplied handlers get run when the handle eventually is created. (This is important b/c the client may set their bindings in the UI's .ctor, before a handle exists.) I want this subscription to be completely transparent, however, and so I also want each event handler to automatically unsubscribe itself from IsHandleCreated
once it's finished running.
I'm still at a point where I'm trying to figure out if this is a good idea, so I haven't generalized the concept yet-- I've only implemented it directly against ToolStripItems in this case to verify that the idea is sound. I'm not sold on it yet, though.
I understand that I also have the option of simply mandating that bindings can only be created once the UI's handle has been created, in the OnLoad event of a form (e.g.). I know that can work, I've done it in the past. I'd like to see if I can ease that particular requirement in this case though. If it's even practical.
Greg,
What you have is not an observer pattern, but rather a message queue. So you're just using the wrong design pattern for the problem you're trying to solve.
Its easy enough to implement your own message queue from scratch using a Queue{Action{object}}
, where objects enqueue themselves, and you simply dequeue items as you invoke them.
The usual way would be to store a boolean value as to whether it should run...
bool runMyEvent = true;
void Handler(object sender, EventArgs e) {
if (runMyEvent) {
// handler here
runMyEvent = false;
} else {
return;
}
}
You can use the run once method here, mentioned a few times here, but there are a couple problems with that, depending on your use case.
1) You may want to re-hook the method later again, and have it run once again. Though I suppose you can reset your bool
2) You still have that reference which could end up keeping your class in memory instead of being garbage collected.
One option is to use an anonymous method and a closure when you define the event handling:
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);
how about a handler that runs only once? Something like this:
if (wasRun) return; wasRun = true;
If it makes sense (and if you have access to the code that calls the handlers) maybe you could just remove all the events after you run the handler.
I don't know if this is a good idea either, just another thought.
You can use WeakReference objects to introduce so to say weak subscribers. During event firing you can check whether weak reference was already collected or not and remove this subscriber from the list of subscribers if necessary.
The basic idea behind this is that when subscribers are GC collected, your handler will notice it and throw them away from the subscribers list.
There is a thorough article on CodeProject: Weak events, which deals with several solutions to this problem.
I usually do something like the following to implement a 1 time event handler.
void OnControlClickOneTime(this Control c, EventHandler e) {
EventHandler e2 = null;
e2 = (s,args) => {
c.Click -= e2;
e(s,args);
};
c.Click += e2;
}
精彩评论