Search code examples
c#disposeidisposable

Advise needed to complete Dispose/Finalize


Following is a stripped-down version of c# code, which helps to capture the PrintScreen key. This work as I expected and tested.

Question: I am aware of deterministic destruction/disposal pattern, which I started to draft as below. However, I need some expert advice to complete my dispose and finalize method. Any advise ?

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

Solution

  • Some suggestions on your code

    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! 
    // }
    }