使用 IDisposable 取消订阅事件

发布于 2024-07-12 11:13:53 字数 1491 浏览 6 评论 0原文

我有一个处理来自 WinForms 控件的事件的类。 根据用户正在执行的操作,我引用该类的一个实例并创建一个新实例来处理同一事件。 我需要首先从事件中取消订阅旧实例 - 很简单。 如果可能的话,我想以非专有的方式执行此操作,这似乎是 IDisposable 的工作。 但是,大多数文档仅在使用非托管资源时建议使用 IDisposable,但这并不适用于此。

如果我实现 IDisposable 并在 Dispose() 中取消订阅事件,我是否会歪曲其意图? 我应该提供一个 Unsubscribe() 函数并调用它吗?


编辑:这是一些虚拟代码,显示了我正在做的事情(使用 IDisposable)。 我的实际实现与一些专有数据绑定有关(长话短说)。

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

在实际代码中,“EventListener”类涉及的比较多,并且每个实例都有唯一的意义。 我在集合中使用它们,并在用户单击时创建/销毁它们。


结论

我接受gbjbaanb的回答,位于至少现在是这样。 我认为使用熟悉的界面的好处超过了在不涉及非托管代码的情况下使用它的任何可能的缺点(该对象的用户如何知道这一点?)。

如果有人不同意 - 请发布/评论/编辑。 如果可以对 IDisposable 提出更好的论据,那么我将更改已接受的答案。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

自由如风 2024-07-19 11:13:53

是的,去做吧。 尽管有些人认为 IDisposable 仅针对非托管资源实现,但事实并非如此 - 非托管资源恰好是最大的胜利,也是实现它的最明显的原因。 我认为它之所以获得这个想法是因为人们想不出任何其他理由来使用它。 它不像终结器,终结器是一个性能问题,而且 GC 不容易处理好。

将所有整理代码放入您的 dispose 方法中。 与试图记住撤消引用相比,它会更清晰、更干净,并且更容易防止内存泄漏,而且更容易正确使用。

IDisposable 的目的是让您的代码更好地工作,而无需您做大量的手动工作。 利用它的力量对你有利,克服一些人为的“设计意图”废话。

我记得当 .NET 首次问世时,说服 Microsoft 确定性终结的有用性是非常困难的 - 我们赢得了这场战斗并说服他们添加它(即使当时它只是一种设计模式),使用它!

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!

甜心小果奶 2024-07-19 11:13:53

我个人的投票是使用取消订阅方法,以便从事件中删除该类。 IDisposable 是一种用于确定性释放非托管资源的模式。 在这种情况下,您不管理任何非托管资源,因此不应实现 IDisposable。

IDisposable 可用于管理事件订阅,但可能不应该。 作为示例,我向您介绍 WPF。 这是一个充满事件和事件处理程序的库。 然而,WPF 中几乎没有类实现 IDisposable。 我认为这表明事件应该以另一种方式进行管理。

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.

一个人的旅程 2024-07-19 11:13:53

使用 IDisposable 模式取消订阅事件时困扰我的一件事是终结问题。

IDisposable 中的 Dispose() 函数应该由开发人员调用,但是,如果开发人员不调用它,则可以理解 GC 将调用这个函数(至少通过标准的 IDisposable 模式)。 但是,在您的情况下,如果您不调用 Dispose ,其他人不会调用 - 事件将保留,并且强引用会阻止 GC 调用终结器。

在我看来,GC 不会自动调用 Dispose() 的事实足以让我们在这种情况下不使用 IDisposable。 也许它需要一个新的应用程序特定接口,该接口表示这种类型的对象必须具有调用的Cleanup函数才能由GC处理。

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.

乖乖兔^ω^ 2024-07-19 11:13:53

另一种选择是使用 弱代表 或类似的东西WPF 弱事件,而不必显式取消订阅。

PS [OT] 我认为只提供强委托的决定是 .NET 平台最昂贵的设计错误。

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.

删除→记忆 2024-07-19 11:13:53

我认为一次性是指 GC 无法自动处理的任何事情,并且事件引用在我的书中很重要。 这是我想出的一个辅助类。

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

    }

它允许您编写以下代码:

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

一个副作用是,您不能在事件上使用 event 关键字,因为这会阻止将它们作为参数传递给辅助构造函数,但是,这似乎不会产生任何不良影响。

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.

空宴 2024-07-19 11:13:53

从我读到的有关一次性用品的所有内容来看,我认为它们实际上主要是为了解决一个问题而发明的:及时释放非托管系统资源。 但我发现的所有示例仍然不仅集中在非托管资源主题上,而且还有另一个共同属性:
调用 Dispose 只是为了加快进程,否则稍后会自动发生(GC -> 终结器 -> dispose)

调用取消订阅事件的 dispose 方法,但永远不会自动发生,即使您添加一个会调用您的 dispose 的终结器。 (至少只要事件拥有对象存在 - 并且如果它被调用,您将不会从取消订阅中受益,因为事件拥有对象无论如何也会消失)

所以主要区别是事件以某种方式构建一个对象无法收集的图,因为事件处理对象突然变成您只想引用/使用的服务的引用。 您突然被迫调用Dispose - 无法自动处置。 因此,Dispose 会得到一个微妙的其他含义,而不是在所有示例中发现的含义,在这些示例中,Dispose 调用(在肮脏的理论中;)- 是不必要的,因为它会被自动调用(在某些时候)......

无论如何。
由于一次性模式已经相当复杂(处理很难正确执行的终结器以及许多指南/合同),更重要的是在大多数情况下与事件反向引用主题无关,我想说它是只要不使用可以称为“从对象图中取消根”/“停止”/“关闭”的隐喻,就可以更容易地在我们的头脑中将其分开。

我们想要实现的是禁用/停止某些行为(通过取消订阅事件)。
最好有一个像 Istoppable 这样带有 Stop() 方法的标准接口,按照约定,它只专注于

  • 让对象(+所有它自己的可停止对象)与任何不是它自己创建的对象的事件断开连接
  • 这样它就不会再以隐式事件样式的方式被调用(因此可以被视为已停止),
  • 一旦对该对象的任何传统引用消失,就可以收集它让

我们调用执行取消订阅的唯一接口方法“Stop()” ”。 您会知道停止的对象处于可接受的状态,但只是停止了。 也许一个简单的属性“Stopped”也是一个不错的选择。

如果您只是想暂停将来肯定会再次需要的某个行为,或者存储已删除的行为,那么拥有一个继承自 Istoppable 的接口“IRestartable”甚至还有一个方法“Restart()”也是有意义的。历史记录中的模型对象以供以后撤消恢复。

写完这篇文章后,我必须承认刚刚在这里看到了 IDisposable 的示例:http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx
但无论如何,在我了解 IObservable 的每个细节和最初动机之前,我会说这不是最好的用例示例,

  • 因为它又是一个非常复杂的系统,我们在这里只有一个小问题
  • ,它可能是其中之一整个新系统的动机是首先消除事件,这将导致与原始问题有关的某种堆栈溢出,

但看起来他们走在正确的轨道上。 无论如何:他们应该使用我的接口“IStoppable”;)因为我坚信 Dispose 存在差异

  • :“你应该调用该方法或可能泄漏 >如果 GC 发生晚了” ....

  • 停止:“你必须调用此方法停止某种行为

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"
冷夜 2024-07-19 11:13:53

IDisposable 坚定地与资源有关,并且是足够多问题的根源,我认为不会让水进一步变得浑浊。

我也在您自己的界面上投票支持取消订阅方法。

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.

递刀给你 2024-07-19 11:13:53

一种选择可能是根本不取消订阅——只是改变订阅的含义。 如果事件处理程序可以足够智能,能够根据上下文知道它要做什么,那么您首先不需要取消订阅。

对于您的特定情况,这可能是也可能不是一个好主意 - 我认为我们没有真正获得足够的信息 - 但值得考虑。

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.

醉酒的小男人 2024-07-19 11:13:53

不,您并没有阻止 IDisposable 的意图。 IDisposable 旨在作为一种通用方法,以确保当您使用完某个对象后,您可以主动清理与该对象相关的所有内容。 它不必只是非托管资源,还可以包括托管资源。 事件订阅只是另一种托管资源!

实践中经常出现的类似场景是,您将在您的类型上实现 IDisposable,纯粹是为了确保您可以在另一个托管对象上调用 Dispose()。 这也不是变态,这只是整洁的资源管理!

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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文