建议需要完成Dispose / Finalize

时间:2013-12-06 08:24:36

标签: c# dispose idisposable

以下是c#代码的精简版本,有助于捕获PrintScreen密钥。这项工作正如我所期待和测试的那样。

问题:我知道确定性的销毁/处置模式,我开始如下所述。但是,我需要一些专家建议来完成我的处理和最终确定方法。有什么建议吗?

public class RegisterPrintKey : IDisposable
    {
        public delegate void HotKeyPass();

        public event HotKeyPass HotKey;

        private IntPtr m_WindowHandle = IntPtr.Zero;

        private MKEY m_ModKey = MKEY.MOD_CONTROL;

        private Keys m_Keys = Keys.A;

        private HotKeyWndProc m_HotKeyWnd = new HotKeyWndProc();


        [DllImport("user32.dll")]
        public static extern bool RegisterHotKey(IntPtr wnd, int id, MKEY mode, Keys vk);

        [DllImport("user32.dll")]
        public static extern bool UnregisterHotKey(IntPtr wnd, int id);

        private class HotKeyWndProc : NativeWindow
        {
            public int m_WParam = 10000;
            public HotKeyPass m_HotKeyPass;

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == 0x0312 && m.WParam.ToInt32() == m_WParam)
                {
                    if (m_HotKeyPass != null) m_HotKeyPass.Invoke();
                }

                base.WndProc(ref m);
            }
        }

        private bool hasDisposed = false;

        protected virtual void Dispose(bool dispose)
        {
            if (hasDisposed) return;

            if (dispose)
            {
                //release objects owned by this instance
                HotKey = null;
                hasDisposed=true;                    


            }
            m_WindowHandle = IntPtr.Zero; // I presume this is not required.
            m_Keys = null; //Do i need to dispose this or relay on stack ( clean up when thread unwind its stack)
            m_ModKey = null;

            m_HotKeyWnd.DestroyHandle();

        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~RegisterPrintKey()
        {
            Dispose(false);
        }
    }

    public enum MKEY
        {
        MOD_ALT = 0x0001,
        MOD_CONTROL = 0x0002,
        MOD_SHIFT = 0x0004,
        MOD_WIN = 0x0008,
        }

2 个答案:

答案 0 :(得分:1)

基本想法是不要触摸您方法的Dispose(false)变体中管理的任何内容。此外,通常不需要将任何内容显式设置为null(特别是在Dispose(false)变体中,这些对象可能已经被垃圾收集了。)

所以,您已经掌握了基本模式,但除了Dispose(false)之外,您不需要在m_HotKeyWnd.DestroyHandle()中执行任何操作。

为了解释一下,当垃圾收集器运行终结器代码时,此对象引用的所有其他托管对象(其他人都没有引用)可能是已收集垃圾。忽略在终结器中管理的所有内容,它可能甚至不再存在。它也是你在本机回调中可以期待的东西(例如,在这种情况下你的WndProc) - 如果回调,你与之交互的托管对象很可能不再存在当对象在终结器队列上时出现。这是托管应用程序与本机代码交互时应用程序崩溃(或至少是意外异常)的一个相当常见的原因。

因此,Dispose(true)应该处理您要清理,管理和取消管理的所有内容(并且您正确使用GC.SuppressFinalize - 您已经处理了非托管资源,因此GC不需要将对象放在终结器队列上,Dispose(false)只能处理非托管位。

编辑:我没有意识到你的m_HotKeyWnd实际上是一个托管对象 - 它自己处理它的非托管资源,你决不应该从终结器中调用它的DestroyHandle。事实上,你甚至不需要终结器,它完全是多余的(因此,有点害)。只需实现一个简单的Dispose(而不是通常的终结器配置模式)来处理m_HotKeyWnd并将其设置为null(在这种情况下,NullReferenceException比AccessViolationException更好或者在处理后使用该对象时可能获得的未定义行为 - 非管理的东西很快就会变得棘手),而且什么都不做。

答案 1 :(得分:1)

有关您的代码的一些建议

public class RegisterPrintKey : IDisposable {
  ...

// This class can allocate the Window Handle resource (HWND)
private class HotKeyWndProc : NativeWindow {
}

// Explicit resource (HWND) allocation
private HotKeyWndProc m_HotKeyWnd = new HotKeyWndProc();

// I'd rather make a property from your "hasDisposed" field:
//   - it's make easier to check instance's state (esp. while debugging)
//   - IsDisposed is more popular name for this
public Boolean IsDisposed {
  get;
  protected set; // <- or even "private set"
}

protected virtual void Dispose(Boolean dispose) {
  if (IsDisposed)
    return;

  if (disposed) {
    // Release any Objects here

    // You've allocated the HWND resource so you have to dispose it: 
    m_HotKeyWnd.DestroyHandle(); // <- Free HWND you've created
  }

  // Here, you may work with structures only!

  // You don't need these assignments, but you can safely do them:
  // mayhaps, they'll be useful for you while debugging 
  m_WindowHandle = IntPtr.Zero; 
  m_Keys = null; 
  m_ModKey = null;

  // Finally, we've done with disposing the instance
  IsDisposed = true; 
}

public void Dispose() {
  Dispose(true);
  // We've done with Dispose: "GC, please, don't bother the disposed instance"
  GC.SuppressFinalize(this);
}

// A treacherous enemy I've commented out: 
// if you've made a error in Dispose() it'll be resource leak 
// or something like AccessViolation. The only good thing is that
// you can easily reproduce (and fix) the problem.
// If you've uncommented ~RegisterPrintKey() this leak/EAV will become
// floating error: it'll appear and disappear on different workstations
// OSs etc: you can't control GC when to run. Floating error is
// much harder to detect.

// Another bad issue with finalizer is that it prevents the instance 
// from being in zero generation, see
// http://stackoverflow.com/questions/12991692/why-doesnt-object-with-finalizer-get-collected-even-if-it-is-unrooted

//~RegisterPrintKey() {
//   // This code can be called (if called!) at random time
//   Dispose(false); // <- That's an enemy! 
// }
}