C# 在CustomControls中取消订阅事件的模式

C# 在CustomControls中取消订阅事件的模式,c#,wpf,events,memory-leaks,windows-runtime,C#,Wpf,Events,Memory Leaks,Windows Runtime,我正在开发一个自定义控件,它在内部订阅Touch.FrameReported-一个静态事件。这有可能导致内存泄漏(在某些情况下确实如此) 这是我目前的解决方案。在已加载/已卸载事件中订阅/取消订阅。但是,我发现卸载的事件并不总是被调用。这可能导致内存泄漏 // Imagine this is a CustomControl, to be consumed by users // with no regard for calling Dispose public class CustomGrid

我正在开发一个自定义控件,它在内部订阅Touch.FrameReported-一个静态事件。这有可能导致内存泄漏(在某些情况下确实如此)

这是我目前的解决方案。在已加载/已卸载事件中订阅/取消订阅。但是,我发现卸载的事件并不总是被调用。这可能导致内存泄漏

// Imagine this is a CustomControl, to be consumed by users 
// with no regard for calling Dispose
public class CustomGrid : Grid
{   
    public CustomGrid()
    {
        Loaded += (s, a) =>
                  {
                      Touch.FrameReported -= OnTouchFrameReported;
                      Touch.FrameReported += OnTouchFrameReported;
                  };

        Unloaded += (s, a) =>
                  {
                    // The intention is to unsubscribe on unload, which should pre-date
                    // user intended 'disposal' of the control
                    Touch.FrameReported -= OnTouchFrameReported;
                  };
    }
有没有一个已知的模式来解决这个问题?取消订阅自定义控件“拆除”中的事件?我已经试过:

  • 取消订阅。并不总是接到电话
  • 处置。无法使用,因为用户可能无法确定地调用Dispose
  • 。不错,但是许多实现不适合WinRT/Silverlight,或者,它们需要显式取消注册,或者,它们仅在调用事件时取消注册(Duh!这是一个弱事件)
  • 终结器。如果存在类似事件处理程序的GC根,终结器不会被阻止吗
      tl;dr如果您使用#1(卸载时取消订阅)和#3(弱事件侦听器)的组合,那么我认为您的控件不应该因为内存泄漏而出错。你已经无能为力了

      实现
      IDisposable
      并没有真正的帮助,因为没有人想在UI元素上调用“Dispose”,而且无论如何,在没有调用“unload”的情况下,要求“Dispose”只会让事情变得更糟。您是对的,如果有一个静态事件作为其调用列表的一部分保存在您的控件上,则不会调用终结器

      我的理解是,当控件从可视化树中移除时,应该调用“unload”。因此,如果“unload”没有在应该被触发的地方被触发,那么要么框架控件某处()中存在一个bug,要么用户代码中存在阻止控件容器被卸载的bug。在这两种情况下,您的控件都不是内存泄漏的来源


      使用弱事件处理程序可能是一种很好的故障保护方法——如果对控件的唯一引用是弱事件侦听器,那么它允许对控件进行GC(因此,这将防止“FrameReported”侦听器导致内存泄漏)。我理解你关于实现的观点——实现起来似乎很棘手,但原则上这项技术没有错(正如你可能知道的,框架使用它作为绑定的事件侦听器)。

      你能确定是什么特定情况导致
      卸载的
      事件没有被引发吗?好主意,但我所看到的弱事件实现(例如[WinRT][1]中的这一个)需要显式取消注册,或者在实际触发事件时延迟取消注册。在我的语境中,这使它们“不弱”。仅当用户触摸触摸屏时才会触发Touch.FrameReported事件,如果控件不在屏幕上,则无法触发该事件。[1]: