Подтвердить что ты не робот

Поля Readonly становятся нулевыми при удалении из финализатора

У меня есть следующий класс. Теперь иногда оператор блокировки бросает ArgumentNullException, и в этом случае я могу увидеть в отладчике, что объект disposelock действительно имеет значение null.

Как я вижу, что утилизация ложна, я знаю, что метод запускается из Finalizer.

Но как это может произойти? Он определяется как readonly и получает его значение при создании объекта.

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 !
       {
           ....
       }
   }
}           
4b9b3361

Ответ 1

В сборке мусора порядок этой коллекции не определен:

  • Первый this собран
  • Далее disposeLock собирается

или

  • Первый disposeLock собирается
  • Далее this собирается

Поэтому не используйте никаких ссылочных полей (такие структуры, как int, bool и т.д.) в Dispose(false);

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

Ответ 2

Все существующие ответы, кроме ответа на отражение, являются ложными. GC не устанавливает ссылки на null при сборе объектов. Доступ к объекту не вызвал ложного отказа из-за GC. Порядок финализации undefined, но все существующие ссылки на объекты продолжают существовать и действительны.

Мое предположение о том, что произошло: конструктор был прерван до того, как поле было инициализировано. Это оставило поле null. Затем финализатор нашел это так.

Конструктор может быть прерван путем выброса исключения или вызовом Thread.Abort, которое является злым.

В сборке мусора порядок этой коллекции не определен

Порядок сбора не является наблюдаемым (за исключением слабых ссылок...). Порядок завершения является наблюдаемым, но не с оператором lock, потому что объекты не теряют возможности синхронизации при завершении.

Ответ 3

Поскольку вы подчеркнули readonly, немного разъясните об этом. Время выполнения не предотвращает изменения полей readonly. Независимо от readonly от С# становится initonly в IL.

Например, вы можете легко изменить поле 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 изменяется (вы столкнулись с одним из них).

Как примечание. Вам действительно нужен финализатор? Я имею в виду, есть ли какие-то истинные неуправляемые ресурсы?