2010-07-15 24 views
9

En mi proyecto actual estoy usando clases que implementan la siguiente interfaz ITransaction que se muestra a continuación. Esta es una interfaz genérica para una transacción que se puede deshacer. También tengo una clase TransactionSet que se utiliza para intentar varias Transacciones o conjuntos de transacciones, y en última instancia se puede usar para crear un árbol de transacciones.¿Está bien disparar eventos desde Dispose()?

Algunas implementaciones de ITransaction mantienen referencias temporales a instancias de objetos o archivos que pueden usar más adelante si hay una llamada al Undo(). Posteriormente se puede confirmar una transacción exitosa después de lo cual ya no se permite Undo() y, por lo tanto, ya no es necesario contar con los datos temporales. Actualmente estoy usando Dispose() como mi método de confirmación para limpiar cualquier recurso temporal.

Sin embargo, ahora me gustaría que mis transacciones también activen eventos para notificar a otras clases de lo que ha sucedido. No quiero que los eventos se activen a menos que se confirme la transacción. Porque no quiero permitir que una transacción active eventos varias veces al deshacerse y luego volver a ejecutar.

Dado que estoy usando Dispose() para confirmar una transacción, ¿hay algún problema con disparar también estos eventos desde ella? ¿O sería mejor tener un método separado Confirm() en mi interfaz que dispara los eventos además de Dispose() que limpia los datos temporales? No puedo pensar en ningún caso en el que me gustaría confirmar, pero no disponer de una transacción. Sin embargo, no está del todo claro para mí lo que debería y no debo hacer dentro de 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; } 
} 

Respuesta

3

IDisposable es simplemente un patrón de diseño integrado en el tiempo de ejecución que facilita la limpieza de objetos de una manera más eficiente que la finalización. Hay muy poco que "no se puede" hacer en un método de eliminación, sin embargo, debe tener cuidado de hacer algunas cosas.

Mientras que el método IDisposable.Dispose() no es un destructor o finalizador "real", puede afectar adversamente la vida útil de un objeto si otros objetos mantienen (o tal vez incluso toman) una referencia al objeto de eliminación durante los eventos de eliminación. Si tiene cuidado con la forma de implementar dicho sistema, puede mitigar los posibles efectos secundarios. Sin embargo, es importante darse cuenta del potencial que ofrece tal implementación ... como una mayor superficie de ataque para que un codificador malicioso explote manteniendo, por ejemplo, sus objetos de transacción vivos indefinidamente.

+0

Varias clases .net tienen un evento "Disposing" y serían muy difíciles de usar sin una. Si un objeto contiene una referencia a un IDisposable que puede usarse o no en otro lugar (por ejemplo, un Control que contiene una Imagen de Fondo), se puede usar un evento de Eliminación para permitir que el titular del objeto principal limpie el objeto anidado cuando sea necesario. – supercat

4

Desechar no es un método especial - no es como un ctor o un finalizador o cualquier cosa - es sólo un modelo útil para notificar a un objeto que el consumidor se hace usando la misma. No hay ninguna razón por la que no pueda generar eventos.

+0

+1. Sí, 'Dispose()' no es el finalizador; haz lo que quieras allí. –

+6

+1. Tenga en cuenta que el patrón "recomendado" de implementación de 'IDisposable' tiene los métodos' Dispose() 'y' Dispose (bool) '. Cuando se invoca 'Dispose (false)', el método * se * llama desde el finalizador, y los eventos * no * pueden levantarse. –

0

Eliminar debe simplemente limpiar. Implementaría los métodos Confirmar() y Revertir(), si se llama a disponer de uno sin llamar a ninguno de ellos primero, es un error que al menos debe registrarse.

0

Por supuesto, puede disparar cualquier evento en el método Dispose. Sin embargo, si desea disparar eventos para confirmar que la transacción está allí, creo que debería tener un método diferente para disparar eventos. Dispose() es la forma de limpiar los recursos internos o disponer de instancias internas como un patrón bien conocido. Una vez que se haya eliminado, la instalación de la transacción no debería estar allí ni ser utilizada. Por lo tanto, puede considerar un método aparte como confirmación de que el temporal no estará disponible, con indicador o estado en Transacción para indicarlo.

1

Conociendo esta pregunta hace 4 años, pero no satisfecho con las respuestas, agrego una que combina algunos de los puntos discutidos en las respuestas y comentarios con aspectos adicionales.

Finalización: Como @jrista señaló, vamos a estar claro que IDisposable no tiene nada que ver con la GC o Finalización per se - es simplemente una convención y la práctica muy recomendable. Sin embargo, usando el Dispose pattern puede llamar al método Dispose de un Finalizer (como señaló @Stephen Cleary). En este caso, no debe plantear ningún evento, nor should it access other managed objects para ese asunto.

Dejando a un lado las cuestiones de Dispose/Finalizer ya que sus clases no necesitan un Finalizer porque no envuelven los recursos no administrados, existen preocupaciones adicionales.

Fugas de memoria/concordancia de tiempo de levantamiento: Este es un problema que a menudo se cita con los eventos y puede aplicarse también a la implementación de su transacción. Cuando tiene un editor de eventos cuya vida útil es superior a la de un suscriptor de eventos, puede tener una pérdida de memoria si ese suscriptor no se da de baja del evento porque el editor se mantendrá aferrado a él. Si sus transacciones duran bastante tiempo y se suscribe a ellas muchos objetos efímeros, debe pensar en implementar disponer en estos objetos y luego darse de baja de la transacción. Ver Should I always disconnect event handlers in the Dispose method?

Principio de la menor sorpresa: ¿Es una buena idea 'abuso' Desechar por la comisión de una transacción? Yo diría que no, aunque hay precedentes. Tome Stream por ejemplo. Usualmente Stream.Dispose se implementa para llamar al Flush y así comprometer los datos al medio subyacente. Sin embargo, tenga en cuenta que todavía tenemos un método Flush explícito, así que debe agregar eso. Encuentro que "disponer de comprometer" viola el principio de menor sorpresa, un método explícito Commit es mucho más claro (todavía puede llamarlo desde Dispose si este es el comportamiento predeterminado que desea).

Event Cascades/Object Object States: Creo que este es el argumento más sólido para no generar eventos en Dispose. Los eventos tienen una tendencia a cascada (es decir, un evento desencadenando otros eventos y códigos) y si no tiene cuidado puede terminar en una situación en la que parte del código decida que sería una buena idea devolver la llamada al objeto que se está eliminando y, por lo tanto, puede estar en un estado inválido. No es divertido depurarlo, ¡especialmente si se puede acceder al objeto por varios hilos! Aunque de nuevo, hay precedentes de esto como Component.Disposed.

Aconsejo que no se generen eventos desde un método Dispose. Cuando termina la vida del editor del evento, ¿realmente importa que todos sus suscriptores actualicen su estado en consecuencia? En la mayoría de los casos, descubro que me estoy deshaciendo de todo el gráfico de objetos (es decir, el editor tiene una vida más larga que los suscriptores). En algunas situaciones, es posible que también desee suprimir activamente cualquier condición de falla que ocurra durante la eliminación (por ejemplo, al cerrar una conexión TCP).

Cuestiones relacionadas