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

StackOverflow https://stackoverflow.com/questions/137621

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

Некоторых людей смущает стандартный одноразовый шаблон и его использование виртуальным методом, 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).Параметр boolean указывает, вызывается ли метод из Dispose() (true) или из деструктора объекта (false).На каждом уровне наследования должен быть реализован метод Dispose (bool) для обработки любой очистки.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top