Можно ли запускать события из Dispose ()? - PullRequest
9 голосов
/ 15 июля 2010

В моем текущем проекте я использую классы, которые реализуют следующий интерфейс ITransaction, показанный ниже.Это общий интерфейс для транзакции, который можно отменить.У меня также есть класс TransactionSet, который используется для выполнения нескольких транзакций или наборов транзакций и в конечном итоге может использоваться для создания дерева транзакций.

Некоторые реализации ITransaction хранят временные ссылки на экземпляры объектов или файлы, которые он может использовать позже, если есть вызов Undo().Успешная транзакция может быть позже подтверждена, после чего Undo() больше не разрешается, и, следовательно, больше нет необходимости во временных данных.В настоящее время я использую Dispose() в качестве метода подтверждения для очистки любых временных ресурсов.

Однако теперь я хотел бы, чтобы мои транзакции также запускали события, чтобы уведомить другие классы о том, что произошло.Я не хочу, чтобы события запускались, если транзакция не подтверждена.Потому что я не хочу, чтобы транзакция запускала события несколько раз, отменяя их, а затем снова запуская.

Поскольку я использую Dispose() для подтверждения транзакции, есть ли что-то не так с активацией этих событийот него?Или было бы лучше иметь отдельный метод Confirm() на моем интерфейсе, который запускает события в дополнение к Dispose(), который очищает временные данные?Я не могу вспомнить ни одного случая, когда я хотел бы подтвердить, но не распорядиться транзакцией.Тем не менее, мне не совсем ясно, что я должен и чего не должен делать в Dispose().

public enum TransactionStatus
{
    NotRun, // the Transaction has not been run, or has been undoed back to the original state
    Successful, ///the action has been run and was successful
    Error //there was an attempt to run the action but it failed
}

/// <summary>
/// Generic transaction interface
/// </summary>
public interface ITransaction
{
    TransactionStatus Status { get; }

    /// <summary>
    /// Attempts the transaction returns true if successful, false if failed.
    /// If failed it is expected that everything will be returned to the original state.
    /// Does nothing if status is already Successful
    /// </summary>
    /// <returns></returns>
    bool Go();

    /// <summary>
    /// Reverts the transaction
    /// Only does something if status is successful.
    /// Should return status to NotRun
    /// </summary>
    void Undo();

    /// <summary>
    /// A message describing the cause of the error if Status == Error
    /// Otherwise equal String.Empty
    /// </summary>
    string ErrorMessage { get; }
}

Ответы [ 5 ]

4 голосов
/ 15 июля 2010

Утилизация - это не специальный метод - это не ctor, финализатор или что-то еще - это всего лишь полезный шаблон, чтобы уведомить объект, который потребитель сделал, используя его.Нет причин, по которым он не может вызывать события.

3 голосов
/ 15 июля 2010

IDisposable - это просто шаблон проектирования, интегрированный во время выполнения, который облегчает очистку объекта более эффективно, чем финализация.Существует очень мало того, что вы не можете сделать в методе утилизации, однако вы должны быть осторожны при выполнении некоторых действий.

Хотя метод IDisposable.Dispose() не является «реальным» деструктором или финализатором, он может отрицательно повлиять на время жизни объекта, если другие объекты сохраняют (или, возможно, даже принимают) ссылку на объект удаления во время событий удаления,Если вы внимательно относитесь к тому, как внедрить такую ​​систему, вы можете уменьшить возможные побочные эффекты.Тем не менее, важно осознавать потенциал, который предлагает такая реализация ... например, увеличенная поверхность атаки для злонамеренного кодера, чтобы, скажем, поддерживать объекты транзакций в течение неопределенного срока.

1 голос
/ 10 сентября 2014

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

Доработка: Как указала @jrista, давайте проясним, что IDisposable не имеет ничего общего с GC или финализацией как таковой - это всего лишь соглашение и настоятельно рекомендуемая практика. Используя шаблон Dispose , вы можете вызвать метод Dispose из финализатора (как указано @Stephen Cleary). В этом случае вы абсолютно не должны вызывать какие-либо события, , и при этом он не должен обращаться к другим управляемым объектам .

Оставляя в стороне проблемы Dispose / Finalizer, поскольку вашим классам не нужен Finalizer, поскольку они не содержат неуправляемые ресурсы, однако существуют и другие проблемы.

Утечки памяти / Соответствие времени жизни: Это часто упоминаемая проблема с событиями, которая также может относиться к вашей реализации транзакции. Если у вас есть издатель событий, срок жизни которого превышает срок действия подписчика на событие, вы можете столкнуться с утечкой памяти, если этот подписчик не откажется от подписки на событие, потому что издатель продолжит удерживать его. Если ваши транзакции являются довольно долгоживущими, и вы подписываете на них много недолговечных объектов, вам следует подумать о реализации dispose в этих объектах, а затем отписаться от транзакции. См. Должен ли я всегда отключать обработчики событий в методе Dispose?

Принцип наименьшего сюрприза: Это хорошая идея, чтобы «злоупотреблять» распоряжаться для совершения транзакции? Я бы сказал нет, хотя есть прецеденты. Возьмите Stream для примера. Обычно Stream.Dispose реализуется для вызова Flush и, таким образом, передает данные на базовый носитель. Однако обратите внимание, что у нас есть явный метод Flush, поэтому вы должны добавить его. Я считаю, что «избавление от коммита» нарушает принцип наименьшего удивления, явный метод Commit намного яснее (вы все равно можете вызывать его с Dispose, если вы хотите использовать это поведение по умолчанию).

Каскады событий / недопустимые состояния объектов: Я думаю, что это самый сильный аргумент в пользу того, чтобы не вызывать события в Dispose. События имеют тенденцию к каскадированию (то есть одно событие вызывает другие события и код), и если вы не будете осторожны, вы можете оказаться в ситуации, когда часть кода решит, что было бы хорошей идеей перезвонить объекту, который удаляется и, таким образом, может быть в недопустимом состоянии. Нет удовольствия отлаживать, особенно если к объекту могут обращаться несколько потоков! Хотя, опять же, для этого есть прецеденты, такие как Component.Disposed .

Я бы посоветовал не вызывать события из метода Dispose. Когда вы заканчиваете время существования издателя событий, действительно ли имеет значение, что все его подписчики соответственно обновляют свое состояние? В большинстве случаев я обнаруживаю, что в любом случае я избавляюсь от всего графа объектов (то есть издатель живет дольше, чем подписчики). В некоторых ситуациях вы также можете активно подавлять любые состояния сбоя, возникающие во время утилизации (например, при закрытии соединения TCP).

0 голосов
/ 15 июля 2010

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

0 голосов
/ 15 июля 2010

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

...