Можно ли вызывать виртуальный метод из Dispose или деструктор?

Я не могу найти ссылку на него, но помню, что читал, что не было хорошей идеей вызывать виртуальные (полиморфные) методы внутри деструктора или метода Dispose() IDisposable.

Это правда, и если да, то может кто-нибудь объяснить, почему?

4 ответа

Решение

Вызов виртуальных методов из финализатора / Dispose небезопасно, по тем же причинам, что небезопасно делать в конструкторе. Невозможно быть уверенным, что производный класс еще не очистил какое-то состояние, которое требуется для правильной работы виртуального метода.

Некоторые люди смущены стандартным шаблоном Disposable и его использованием виртуального метода, virtual Dispose(bool disposing) и думаю, что это нормально использовать любой виртуальный метод во время утилизации. Рассмотрим следующий код:

class C : IDisposable {
    private IDisposable.Dispose() {
        this.Dispose(true);
    }
    protected virtual Dispose(bool disposing) {
        this.DoSomething();
    }

    protected virtual void DoSomething() {  }
}
class D : C {
    IDisposable X;

    protected override Dispose(bool disposing) {
        X.Dispose();
        base.Dispose(disposing);
    }

    protected override void DoSomething() {
        X.Whatever();
    }
}

Вот что происходит, когда вы утилизируете и объект типа D, называется d:

  1. Некоторые вызовы кода ((IDisposable)d).Dispose()
  2. C.IDisposable.Dispose() вызывает виртуальный метод D.Dispose(bool)
  3. D.Dispose(bool) располагает D.X
  4. D.Dispose(bool) звонки C.Dispose(bool) статически (цель вызова известна во время компиляции)
  5. C.Dispose(bool) вызывает виртуальные методы D.DoSomething()
  6. D.DoSomething вызывает метод D.X.Whatever() на уже распорядился D.X
  7. ?

Теперь, большинство людей, которые запускают этот код, делают одну вещь, чтобы исправить это - они перемещают base.Dispose(dispose) позвоните, прежде чем они уберут свой собственный объект. И да, это работает. Но действительно ли вы доверяете Программисту X, Ультра-младшему разработчику из компании, которую вы разработали? C для, назначено писать D, чтобы записать его так, чтобы ошибка была обнаружена или base.Dispose(disposing) позвонить в нужное место?

Я не говорю, что вы никогда не должны писать код, который вызывает виртуальный метод из Dispose, просто вам нужно документировать требование этого виртуального метода, чтобы он никогда не использовал состояние, определенное в любом классе, производном ниже. C,

Я не верю, что есть какие-либо рекомендации против вызова виртуальных методов. Запрет, который вы помните, может быть правилом против ссылки на управляемые объекты в финализаторе.

Существует стандартная схема, определяющая.Net документацию о том, как следует реализовывать Dispose(). Шаблон очень хорошо продуман, и за ним следует внимательно следить.

Суть в следующем: Dispose() - это не виртуальный метод, который вызывает виртуальный метод Dispose(bool). Логический параметр указывает, вызывается ли метод из Dispose() (true) или деструктора объекта (false). На каждом уровне наследования должен быть реализован метод Dispose (bool) для обработки любой очистки.

Когда Dispose (bool) передается значение false, это означает, что финализатор вызвал метод dispose. В этом случае следует пытаться очистить только неуправляемые объекты (за исключением некоторых редких случаев). Причина этого заключается в том, что сборщик мусора только что вызвал метод finalize, поэтому текущий объект должен быть помечен как готовый к финализации. Следовательно, любой объект, на который он ссылается, также может быть помечен как готовый к завершению, и, поскольку последовательность недетерминирована, финализация может уже иметь место.

Я настоятельно рекомендую поискать шаблон Dispose() в документации.Net и точно следовать ему, потому что он, вероятно, защитит вас от странных и сложных ошибок!

Виртуальные методы не приветствуются как в конструкторах, так и в деструкторах.

Причина более практична: виртуальные методы могут быть переопределены любым способом, выбранным переопределителем, и должны быть обеспечены такие вещи, как инициализация объекта, например, во время конструирования, чтобы в итоге вы не получили объект со случайными нулями и недопустимым государство.

Чтобы расширить ответ Джона, вместо вызова виртуальных методов вы должны переопределить dispose или destructor для подклассов, если вам нужно обрабатывать ресурсы на этом уровне.

Хотя я не верю, что здесь есть "правило" в отношении поведения. Но общая мысль заключается в том, что вы хотите изолировать очистку ресурсов только от этого экземпляра на этом уровне реализации.

Другие вопросы по тегам