Pregunta

Tengo una clase que maneja eventos desde un control WinForms. Según lo que está haciendo el usuario, estoy defendiendo una instancia de la clase y creando una nueva para manejar el mismo evento. Primero necesito cancelar la suscripción de la instancia anterior del evento, lo cual es bastante fácil. Me gustaría hacer esto de una manera no propietaria si es posible, y parece que este es un trabajo para IDisposable. Sin embargo, la mayoría de la documentación recomienda IDisposable solo cuando se utilizan recursos no administrados, lo que no se aplica aquí.

Si implemento IDisposable y me doy de baja del evento en Dispose (), ¿estoy pervirtiendo su intención? ¿Debería proporcionar una función Unsubscribe () y llamar a eso?


Editar: Aquí hay un código ficticio que muestra lo que estoy haciendo (usando IDisposable). Mi implementación real está relacionada con algunos enlaces de datos patentados (larga historia).

class EventListener : IDisposable
{
    private TextBox m_textBox;

    public EventListener(TextBox textBox)
    {
        m_textBox = textBox;
        textBox.TextChanged += new EventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, EventArgs e)
    {
        // do something
    }

    public void Dispose()
    {
        m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
    }
}

class MyClass
{
    EventListener m_eventListener = null;
    TextBox m_textBox = new TextBox();

    void SetEventListener()
    {
        if (m_eventListener != null) m_eventListener.Dispose();
        m_eventListener = new EventListener(m_textBox);
    }
}

En el código real, el "EventListener" la clase está más involucrada, y cada instancia es singularmente significativa. Los uso en una colección y los creo / destruyo a medida que el usuario hace clic.


Conclusión

Acepto respuesta de gbjbaanb , al menos por ahora. Siento que el beneficio de usar una interfaz familiar supera cualquier posible inconveniente de usarlo donde no está involucrado ningún código no administrado (¿cómo podría saberlo un usuario de este objeto?).

Si alguien no está de acuerdo, publique / comente / edite. Si se puede hacer un mejor argumento contra IDisposable, entonces cambiaré la respuesta aceptada.

¿Fue útil?

Solución

Sí, adelante. Aunque algunas personas piensan que IDisposable se implementa solo para recursos no administrados, este no es el caso: los recursos no administrados son la mayor ganancia y la razón más obvia para implementarlo. Creo que ha adquirido esta idea porque a la gente no se le ocurre ninguna otra razón para usarla. No es como un finalizador, que es un problema de rendimiento y no es fácil para el GC manejarlo bien.

Ponga cualquier código ordenado en su método de eliminación. Será más claro, más limpio y significativamente más probable que evite pérdidas de memoria y una vista más fácil de usar correctamente que intentar recordar deshacer sus referencias.

La intención de IDisposable es hacer que su código funcione mejor sin que tenga que hacer mucho trabajo manual. Use su poder a su favor y supere alguna `` intención de diseño '' artificial. sin sentido.

Recuerdo que fue lo suficientemente difícil de persuadir a Microsoft de la utilidad de la finalización determinista cuando salió .NET por primera vez: ganamos la batalla y los persuadimos para que la agregaran (incluso si solo era un patrón de diseño en ese momento), úsalo!

Otros consejos

Mi voto personal sería tener un método de cancelación de suscripción para eliminar la clase de los eventos. IDisposable es un patrón destinado a la liberación determinista de recursos no administrados. En este caso, no administra ningún recurso no administrado y, por lo tanto, no debe implementar IDisposable.

IDisposable se puede usar para administrar suscripciones a eventos, pero probablemente no. Por un ejemplo, te señalo a WPF. Esta es una biblioteca llena de eventos y controladores de eventos. Sin embargo, prácticamente ninguna clase en WPF implementa IDisposable. Tomaría eso como una indicación de que los eventos deberían gestionarse de otra manera.

Una cosa que me molesta sobre el uso del patrón IDisposable para cancelar la suscripción a los eventos es el problema de Finalización.

La función

Dispose () en IDisposable debe ser llamada por el desarrollador, SIN EMBARGO, si no es llamada por el desarrollador, se entiende que el GC llamará a esta función (por el patrón estándar IDisposable , al menos). Sin embargo, en su caso, si no llama a Dispose , nadie más lo hará: el evento permanece y la fuerte referencia impide que GC llame al finalizador.

El simple hecho de que Dispose () no sea llamado automáticamente por GC me parece suficiente para no usar IDisposable en este caso. Quizás requiera una nueva interfaz específica de la aplicación que diga que este tipo de objeto debe tener una función Cleanup llamada para ser eliminada por GC.

Creo que desechable es para todo lo que GC no puede encargarse automáticamente, y las referencias de eventos cuentan en mi libro. Aquí hay una clase auxiliar que se me ocurrió.

public class DisposableEvent<T> : IDisposable
    {

        EventHandler<EventArgs<T>> Target { get; set; }
        public T Args { get; set; }
        bool fired = false;

        public DisposableEvent(EventHandler<EventArgs<T>> target)
        {
            Target = target;
            Target += new EventHandler<EventArgs<T>>(subscriber);
        }

        public bool Wait(int howLongSeconds)
        {
            DateTime start = DateTime.Now;
            while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
            {
                Thread.Sleep(100);
            }
            return fired;
        }

        void subscriber(object sender, EventArgs<T> e)
        {
            Args = e.Value;
            fired = true;
        }

        public void Dispose()
        {
            Target -= subscriber;
            Target = null;
        }

    }

que le permite escribir este código:

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

Un efecto secundario, no debe usar la palabra clave de evento en sus eventos, ya que eso evita pasarlos como un parámetro al constructor auxiliar, sin embargo, parece que no tiene ningún efecto negativo.

De todo lo que leí sobre los desechables, diría que en realidad fueron inventados principalmente para resolver un problema: liberar los recursos del sistema no administrados de manera oportuna. Pero aún así todos los ejemplos que encontré no solo se centran en el tema de los recursos no administrados, sino que también tienen otra propiedad en común: Dispose se llama solo para acelerar un proceso que de otro modo habría ocurrido más tarde de forma automática (GC - > finalizer - > dispose)

Llamar a un método de eliminación que se da de baja de un evento, sin embargo, nunca se produciría automáticamente, incluso si agrega un finalizador que llame a su eliminación. (al menos no mientras exista el objeto propietario del evento, y si se lo llamara, no se beneficiaría de la cancelación de la suscripción, ya que el objeto propietario del evento también desaparecería)

Entonces, la principal diferencia es que los eventos de alguna manera crean un gráfico de objeto que no se puede recopilar, ya que el objeto de manejo de eventos de repente se hace referencia al servicio que solo quería referenciar / usar. De repente, está obligado a llamar a Descartar: no es posible deshacerse de automático . Dispose obtendría un significado sutil diferente al que se encuentra en todos los ejemplos en los que una llamada Dispose, en teoría sucia;), no es necesaria, ya que se llamaría automáticamente (en algún momento) ...

De todos modos. Dado que el patrón desechable es algo que ya es bastante complicado (trata con finalizadores que son difíciles de acertar y muchas pautas / contratos) y, lo que es más importante en la mayoría de los puntos, no tiene nada que ver con el tema de referencia del evento, diría que sería es más fácil separar eso en nuestras cabezas simplemente no usando esa metáfora para algo que podría llamarse "desrootear del gráfico de objeto" / " detener " / " apague " ;.

Lo que queremos lograr es deshabilitar / detener algún comportamiento (al darse de baja de un evento). Sería bueno tener una interfaz estándar como IStoppable con un método Stop (), que por contrato solo se centra en

  • conseguir que el objeto (+ todos sus propios stoppables) se desconecte de los eventos de cualquier objeto que no haya creado por sí mismo
  • para que ya no se llame de manera implícita al estilo de evento (por lo tanto, puede percibirse como detenido)
  • se puede recopilar tan pronto como desaparezcan las referencias tradicionales sobre ese objeto

Llamemos al único método de interfaz que cancela la suscripción " Stop () " ;. Sabría que el objeto detenido está en un estado aceptable pero solo está detenido. Tal vez una propiedad simple "Detenido" también sería bueno tener.

Incluso tendría sentido tener una interfaz "IRestartable" que hereda de IStoppable y además tiene un método "Reiniciar ()" si solo desea pausar un determinado comportamiento que seguramente será necesario nuevamente en el futuro, o almacenar un objeto modelo eliminado en un historial para deshacer la recuperación posterior.

Después de todo lo escrito, debo confesar que acabo de ver un ejemplo de IDisposable en algún lugar aquí: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Pero de todos modos, hasta que obtenga todos los detalles y la motivación original de IObservable, diría que no es el mejor ejemplo de caso de uso

  • ya que nuevamente es un sistema bastante complicado a su alrededor y solo tenemos un pequeño problema por aquí
  • y podría ser que una de las motivaciones de ese sistema completamente nuevo es deshacerse de los eventos en primer lugar, lo que resultaría en una especie de desbordamiento de la pila con respecto a la pregunta original

Pero parece que están en el camino correcto. De todos modos: deberían haber usado mi interfaz '' IStoppable '' ;) ya que creo firmemente que hay una diferencia en

IDisposable está firmemente relacionado con los recursos, y creo que es la fuente de suficientes problemas para no enturbiar aún más las aguas.

También estoy votando por un método para darse de baja en su propia interfaz.

Una opción puede ser no darse de baja en absoluto, solo cambiar el significado de la suscripción. Si el controlador de eventos se puede hacer lo suficientemente inteligente como para saber lo que debe hacer en función del contexto, no necesita darse de baja en primer lugar.

Eso puede o no ser una buena idea en su caso particular, no creo que realmente tengamos suficiente información, pero vale la pena considerarlo.

Otra opción sería utilizar delegados débiles o algo así como eventos débiles de WPF , en lugar de tener que darse de baja explícitamente .

P.S. [OT] Considero la decisión de proporcionar solo delegados fuertes el error de diseño más costoso de la plataforma .NET.

No, no estás evitando la intención de IDisposable. IDisposable pretende ser una forma de uso múltiple para garantizar que cuando termine de usar un objeto, pueda limpiar de forma proactiva todo lo relacionado con ese objeto. No tiene que ser solo recursos no administrados, también puede incluir recursos administrados. ¡Y una suscripción a un evento es solo otro recurso administrado!

Un escenario similar que surge con frecuencia en la práctica es que implementará IDisposable en su tipo, simplemente para asegurarse de que puede llamar a Dispose () en otro objeto administrado. ¡Esto tampoco es una perversión, es solo una ordenada gestión de recursos!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top