在GC中收集在构造函数中初始化的对象的方式与在方法中初始化的对象相同吗?

时间:2014-07-23 18:03:14

标签: c# .net memory-management garbage-collection

我的示例是在托管代码中使用非托管代码。

如果我在方法中实例化一个类级字段,GC将在收集我的类的实例之前收集此字段,如下所示:

public class SpyAgent
{
   public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);

   private WindowProcedureDelegate _windowProcedure;

   public void SpyAgent()
   {
   }

   ...

   public void SomeRandomMethod()
   {
      ...

      //GC will collect this instance before SpyAgent dispose.
      _windowProcedure = new WindowProcedureDelegate(WindowProcedure);

      SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
   }

   [DllImport("user32.dll")]
   private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);

   ...
}

使用系统一分钟后,我收到以下错误:

  

托管调试助手'CallbackOnCollectedDelegate'已检测到   一个问题   'C:\ Acaz \ Supervise.Bootstrap \ Supervise.Bootstrap \ Supervise.Bootstrap \ Supervise.Bootstrap \ CONTOSO \ BIN \调试\ Contoso.vshost.exe'。

     

附加信息:对垃圾收集的类型委托进行了回调   'Supervise.Application!Supervise.Application.SpyAgent + WindowProcedureDelegate ::调用'。   这可能会导致应用程序崩溃,损坏和数据丢失。什么时候   将代理传递给非托管代码,他们必须保持活着   管理应用程序,直到它保证永远不会   调用。

但是当我在构造函数中实例化委托时,我没有更多的错误。

public class SpyAgent
{
   public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);

   private WindowProcedureDelegate _windowProcedure;

   public void SpyAgent()
   {
      //This instance will be collected by GC just when SpyAgent start to being collected too.
      _windowProcedure = new WindowProcedureDelegate(WindowProcedure);
   }

   ...

   public void SomeRandomMethod()
   {
      ...

      SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
   }

   [DllImport("user32.dll")]
   private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);

   ...
}

为什么会这样?为什么在收集类之前收集在方法中初始化类级别的字段。没有意义,因为该字段是在类级别声明的,可以用于整个类。我将引用存储在类级别的项目中。

为什么当我在构造函数中初始化字段时,在收到类之前才收集字段?

垃圾收集器与方法和构造函数中初始化的对象的行为有何不同?

3 个答案:

答案 0 :(得分:4)

代码段似乎与您遇到问题的真实代码非常不匹配。但核心问题是本机代码依赖于委托对象。垃圾收集器可以看到该依赖关系,它对GetWindowLong()一无所知。这使得很有可能过早收集委托对象,最常见的是在下一次收集期间。当Windows传递消息时,这将导致硬崩溃,当您从调试器运行时,Managed Debugger Assistant会在它产生一个响亮的不可诊断的爆炸之前告诉您它。

与.NET Framework一样,您不应该自己编写此代码。对窗口进行子类化是一个非常常见的需求,Winforms和WPF也需要这样做。而不是从scatch中转移自己的类,而是从NativeWindow class派生自己的类。覆盖WndProc()方法以实现您自己的消息处理。当您准备子窗口时,调用其AssignHandle()方法。当您看到WM_NCDESTROY消息时,在WndProc覆盖中调用ReleaseHandle(),该消息是在窗口过期之前发送到窗口的最后一条消息。 NativeWindow负责委托管道。例如:

class SpyAgent : NativeWindow {
    public SpyWindow(IntPtr hWnd) {
        this.AssignHandle(hWnd);
    }
    protected override void WndProc(ref Message m) {
        if (m.Msg == 130) this.ReleaseHandle();
        // Your code here
        //...
        base.WndProc(ref m);
    }
}

答案 1 :(得分:0)

无法保证调用终结器的顺序。这两个对象都是因为无根并且有资格同时进行垃圾回收。你应该阅读关于“复活”的GC概念,其中一个没有生根但仍然可以从终结器队列中到达的对象再次生根(通过提供对有根对象的引用)。

实际上,构造顺序和内存顺序可能会影响终结顺序,但这没有记录。不要依赖它,因为它可以在.NET版本之间进行更改。

答案 2 :(得分:0)

我发现了这个问题。

是因为我多次调用SomeRandom方法。每当我对该字段执行新的委托归属时,我都丢失了过去的引用,然后GC收集了这个过去的实例,因为没有引用它。 :)