Можно ли вызывать виртуальный метод из Dispose или деструктор? - PullRequest
2 голосов
/ 26 сентября 2008

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

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

Ответы [ 4 ]

5 голосов
/ 26 сентября 2008

Вызов виртуальных методов из финализатора / 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

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

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

1 голос
/ 26 сентября 2008

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

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

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

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

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

1 голос
/ 26 сентября 2008

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

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

0 голосов
/ 26 сентября 2008

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

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

...