从终结器处理时,只读字段变为空

Readonly fields becomes null when disposing from finalizer

我有以下 class。现在有时 lock 语句会抛出 ArgumentNullException,在这种情况下,我可以在调试器中看到 disposelock 对象确实为空。

正如我所看到的,disposing 是 false,我知道该方法是从 Finalizer 触发的。

但这怎么会发生呢?它被定义为只读,并在创建对象时获取其值。

PS:我知道这不是一个好的模式,但它是给定代码的一部分,我只是无法解释为什么它会变成 null

public abstract class DisposableMarshalByRefObject : MarshalByRefObject, IDisposable
{
    private readonly object disposeLock = new object();


   /// </summary>
   ~DisposableMarshalByRefObject()
   {
       Dispose(false);
   }

   /// <summary>
   /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
   /// </summary>
   public void Dispose()
   {
       Dispose(true);
       GC.SuppressFinalize(this);
   }

   protected void Dispose(bool disposing) //disposing = false,=> finalizer
   {
       lock (disposeLock) //ArgumentNull Exception !
       {
           ....
       }
   }
}           

关于垃圾collectioncollection的顺序未定义:

  1. this个收藏
  2. 下一篇disposeLock收藏

  1. disposeLock
  2. 下一篇this收藏

所以不要使用任何 reference 字段(structs like int, bool 等是安全的) 在 Dispose(false);

protected virtual void Dispose(bool disposing) {
  if (disposing) {
    // Explicit disposing: it's safe to use disposeLock 
    lock (disposeLock) {
      ...
    }
  } 
  else {
    // Garbage collection: there's no guarantee that disposeLock has not been collected
  }
}

既然您强调了 readonly,请稍微澄清一下。运行时不会阻止 readonly 字段被修改。无论 C# 中的 readonly 变为 IL 中的 initonly

例如,您可以使用反射轻松修改 readonly 字段:

class A
{
    private readonly int bar;

    public A()
    {
        bar = 1;
    }

    public void Foo()
    {
        Console.WriteLine(bar);
    }
}

var a = new A();

a.Foo(); // displays "1"
a.GetType().GetField("bar", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(a, 2);
a.Foo(); // displays "2"

当然,这并不意味着您每次都应该针对 null 测试这些字段,但是当 readonly 字段被修改(您遇到过其中一个)。

作为旁注。你真的需要终结器吗?我的意思是,是否有任何 true 非托管资源?

除反思答案外,所有现有答案均为错误。 GC 在收集对象时不会将引用设置为 null。对象访问不会因 GC 而虚假地失败。最终确定的顺序未定义,但所有存在的对象引用将继续存在并且有效。

我对发生的事情的猜测:构造函数在字段初始化之前中止。那离开了字段 null。终结器后来发现它是这样的。

可以通过抛出异常或调用 Thread.Abort 来中止构造函数,这是邪恶的。

On garbage collection the order of that collection is not defined

collection 的顺序不是 observable(除非通过弱引用...)。 finalization 的顺序是可以观察到的,但不能用 lock 语句,因为对象在完成时不会失去同步的能力。