问题
I have a class that handles events from a WinForms control. Based on what the user is doing, I am deferencing one instance of the class and creating a new one to handle the same event. I need to unsubscribe the old instance from the event first - easy enough. I\'d like to do this in a non-proprietary manner if possible, and it seems like this is a job for IDisposable. However, most documentation recommends IDisposable only when using unmanaged resources, which does not apply here.
If I implement IDisposable and unsubscribe from the event in Dispose(), am I perverting its intention? Should I instead provide an Unsubscribe() function and call that?
Edit: Here\'s some dummy code that kind of shows what I\'m doing (using IDisposable). My actual implementation is related to some proprietary data binding (long story).
class EventListener : IDisposable
{
private TextBox m_textBox;
public EventListener(TextBox textBox)
{
m_textBox = textBox;
textBox.TextChanged += new EventHandler(textBox_TextChanged);
}
void textBox_TextChanged(object sender, EventArgs e)
{
// do something
}
public void Dispose()
{
m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
}
}
class MyClass
{
EventListener m_eventListener = null;
TextBox m_textBox = new TextBox();
void SetEventListener()
{
if (m_eventListener != null) m_eventListener.Dispose();
m_eventListener = new EventListener(m_textBox);
}
}
In the actual code, the \"EventListener\" class is more involved, and each instance is uniquely significant. I use these in a collection, and create/destroy them as the user clicks around.
Conclusion
I\'m accepting gbjbaanb\'s answer, at least for now. I feel that the benefit of using a familiar interface outweighs any possible downside of using it where no unmanaged code is involved (how would a user of this object even know that?).
If anyone disagrees - please post/comment/edit. If a better argument can be made against IDisposable, then I\'ll change the accepted answer.
回答1:
Yes, go for it. Although some people think IDisposable is implemented only for unmanaged resources, this is not the case - unmanaged resources just happens to be the biggest win, and most obvious reason to implement it. I think its acquired this idea because people couldn't think of any other reason to use it. Its not like a finaliser which is a performance problem and not easy for the GC to handle well.
Put any tidy-up code in your dispose method. It'll be clearer, cleaner and significantly more likely to prevent memory leaks and a damn sight easier to use correctly than trying to remember to un-do your references.
The intention of IDisposable is to make your code work better without you having to do lots of manual work. Use its power in your favour and get over some artificial "design intention" nonsense.
I remember it was difficult enough to persuade Microsoft of the usefulness of deterministic finalisation back when .NET first came out - we won the battle and persuaded them to add it (even if it was only a design pattern at the time), use it!
回答2:
My personal vote would be to have an Unsubscribe method in order to remove the class from events. IDisposable is a pattern intended for deterministic release of unmanaged resources. In this case you not managing any unmanaged resources and therefore should not be implementing IDisposable.
IDisposable can be used to manage event subscriptions but probably shouldn't. For an example I point you to WPF. This is a library rife with events and event handlers. Yet virtually no class in WPF implements IDisposable. I would take that as an indication that events should be managed another way.
回答3:
One thing that bothers me about using IDisposable
pattern for unsubscribe from events is Finalization issue.
Dispose()
function in IDisposable
is supposed to be called by the developer, HOWEVER, if it isn't called by the developer, it is a understood that the GC will call this function (by the standard IDisposable
pattern, at least). In your case, however, if you don't call Dispose
no one else will - The event remains and the strong reference holds GC from calling the finalizer.
The mere fact that Dispose
() won't be called automatically by GC seems to me enough to not to use IDisposable in this case. Perhaps it calls for a new application specific interface that says that this type of object must have a Cleanup function called to be disposed by GC.
回答4:
I think disposable is for anything that GC can't take care of automatically, and event references count in my book. Here is a helper class I came up with.
public class DisposableEvent<T> : IDisposable
{
EventHandler<EventArgs<T>> Target { get; set; }
public T Args { get; set; }
bool fired = false;
public DisposableEvent(EventHandler<EventArgs<T>> target)
{
Target = target;
Target += new EventHandler<EventArgs<T>>(subscriber);
}
public bool Wait(int howLongSeconds)
{
DateTime start = DateTime.Now;
while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
{
Thread.Sleep(100);
}
return fired;
}
void subscriber(object sender, EventArgs<T> e)
{
Args = e.Value;
fired = true;
}
public void Dispose()
{
Target -= subscriber;
Target = null;
}
}
which lets you write this code :
Class1 class1 = new Class1();
using (var x = new DisposableEvent<object>(class1.Test))
{
if (x.Wait(30))
{
var result = x.Args;
}
}
One side effect, you must not use the event keyword on your events, since that prevents passing them as a parameter to the helper constructor, however, that seems to not have any ill effects.
回答5:
From all that i read about disposables i would argue that they were really mainly invented for solving one problem: freeing unmanaged system resources in a timely manner. But still all examples that i found are not only focussed on the topic of unmanaged resources, but also have another property in common: Dispose is called just to speed up a process that otherwise would have occured later on automatically (GC -> finalizer -> dispose)
Calling a dispose method that unsubscribes from an event however would never occur automatically, even if you would add a finalizer that would call your dispose. (at least not as long as the event owning object exists - and if it would be called you wouldn't benefit from the unsubscription, since the event owning object would also be gone anyway)
So the main difference is that events somehow build an object graph that can't be collected, since the event handling object suddenly becomes referenced of the service that you just wanted to reference/use. You suddenly are forced to call Dispose - no automatic disposal is possible. Dispose would thereby get a subtle other meaning than that found in all examples where a Dispose call - in dirty theory ;) - isn't necessary, since it would get called automaically (at some time)...
Anyway. Since the disposable pattern is something that already is pretty complicated (deals with finalizers that are hard to get right and many guidelines / contracts) and more importantly in most points has nothing to do with the event back referencing topic, i would say it would be easier to get that separeted in our heads by just not using that metaphor for something that could be called "unroot from object graph" / "stop" / "turn off".
What we want to achieve is to disable / stop some behaviour (by unsubscribing from an event). It would be nice to have a standard interface like IStoppable with a Stop() method, that by contract is just focussed on
- getting the object (+ all its own stoppables) disconnected from events of any objects that it didn't create by its own
- so that it won't get called in implicit event style manner any longer (therefore can be perceived as stopped)
- can be collected as soon any traditional references onto that object are gone
Let's call the only interface method that does the unsubscription "Stop()". You would know that the stopped object is in a acceptable state but only is stopped. Maybe a simple property "Stopped" would also be a nice to have.
It even would make sense to have an interface "IRestartable" that inherits from IStoppable and additionally has a method "Restart()" if you just want to pause a certain behaviour that certainly will be needed again in the future, or to store a deleted model object in a history for later undo recovery.
After all writing i have to confess to have just seen an example of IDisposable somewhere over here: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx But anyway until i get every detail and the original motivation of IObservable i would say it is not the best use case example
- since again it is a pretty complicated system around it and we only have a small problem over here
- and it might be that one of the motivations of that that whole new system is to get rid of events in the first place, which would result in a sort of stack overflow concerning th e original question
But it seems they are on some right track. Anyway: they should have used my interface "IStoppable" ;) since i strongly believe there is a difference in
- Dispose: "you should call that method or something might leak if the GC happens to late" ....
and
- Stop: "you have to call this method to stop a certain behaviour"
回答6:
IDisposable is firmly about resources, and the source of enough problems to not muddy the waters further I think.
I'm voting for an Unsubscribe method on your own Interface too.
回答7:
One option may be not to unsubscribe at all - just to change what the subscription means. If the event handler could be made smart enough to know what it's meant to do based on the context, you don't need to unsubscribe in the first place.
That may or may not be a good idea in your particular case - I don't think we've really got enough information - but it's worth considering.
回答8:
Another option would be to use weak delegates or something like WPFs weak events, instead of having to unsubscribe explicitly.
P.S. [OT] I consider the decision to only provide strong delegates the single most expensive design mistake of the .NET platform.
回答9:
No, you're not preventing the intention of IDisposable. IDisposable is intended as an all-purpose way to ensure that when you're done using an object, you can proactively clean up everything tied to that object. It doesn't have to be only unmanaged resources, it can include managed resources too. And an event subscription is just another managed resource!
A similar scenario that frequently arises in practice is that you will implement IDisposable on your type, purely in order to ensure you can call Dispose() on another managed object. This is not a perversion either, it is just tidy resource management!
来源:https://stackoverflow.com/questions/452281/using-idisposable-to-unsubscribe-events