c# - net - ¿Cómo puedo convencer a mis colegas de que no implementen IDisposable en todo?
implements idisposable dispose c# (11)
Trabajo en un proyecto en el que hay una gran cantidad de objetos en instancias de algunas clases que permanecen en la memoria durante toda la vida de la aplicación. Hay muchas fugas de memoria causadas con OutOfMemoryExceptions lanzadas de vez en cuando. Parece que después de que los objetos ejemplificados fuera de alcance, no se están recolectando basura.
He aislado el problema de tratarme principalmente de los controladores de eventos que se adjuntan al objeto de larga vida que nunca se desprenden, lo que hace que el objeto de larga duración aún tenga una referencia a los objetos fuera de alcance, que nunca serán basura recogida
La solución que propusieron mis colegas es la siguiente: Implementar IDisposable en todas las clases, en todos los ámbitos y en el método de Disposición, anule todas las referencias en sus objetos y separe de todos los eventos que adjuntó.
Creo que esta es una muy mala idea. En primer lugar porque es excesivo, ya que el problema puede resolverse en gran parte solucionando algunas áreas problemáticas y, en segundo lugar, porque el propósito de IDisposable es liberar cualquier recurso no administrado que controlen sus objetos, no porque no confíe en el recolector de basura. Hasta ahora mis argumentos han caído en oídos sordos. ¿Cómo puedo convencerlos de que esto es inútil?
Crear el deber de resolver una excepción más OutOfMemoryException. Cuando alguien resuelve los errores que pertenecen a otros, aumenta la responsabilidad en el propio código. En la etapa inicial de nuestro proyecto se nos recomendó "bandera del tonto": el responsable de un error fatal obtiene esta bandera por un día.
El solo hecho de implementar Dispose()
en todos los tipos no va a resolver su problema. Recuerde que Dispose()
no se llama automáticamente y no tiene nada que ver con la recuperación de la memoria administrada. Para tener algún efecto en sus métodos de Dispose()
, debe llamarlo en cualquier lugar relevante, ya sea explícitamente o mediante el using
.
En otras palabras, solo implementar IDisposable
all around no resolverá mágicamente sus problemas porque los métodos Dispose()
no serán llamados a menos que también cambie el uso de cada tipo en su código.
Sin embargo, no recomendaría implementar IDisposable
en todos sus tipos, simplemente porque no tiene sentido. La interfaz se usa para indicar que el tipo en cuestión usa algún recurso, que no es manejado por el recolector de basura.
Las referencias de eventos son manejadas por el recolector de basura. Solo debe darse de baja si su editor vive significativamente más tiempo que sus suscriptores. Una vez que el editor muere, los suscriptores también morirán.
En general, si registra un controlador con un evento, "anular el registro" es exactamente el tipo de limpieza básica que debe hacer cada objeto al ser destruido. Si no tiene destructores en el idioma, entonces debe definir un método al que llamar para decirle al objeto que desaparecerá. Ese método debería limpiar los manejadores de eventos.
¿No es esto para lo que es idóneo? ¿Por qué necesitarías otra solución si ya está ahí? ¿Y por qué esos nudillos no implementaron sus objetos correctamente en primer lugar? ;)
Es difícil lograrlo, pero he encontrado que la mejor manera de hacer que la gente haga lo que quieres es dejar que piensen que es su idea. Los conoces mejor que yo, pero frases como "Si solo hubiera una forma de descubrir por qué los objetos están colgando por tanto tiempo" y "Me gustaría saber más sobre los eventos relacionados con los objetos" podrían ser un punto de partida.
IDisposable solo debe utilizarse para liberar recursos no administrados (SafeHandles y similares), y el método Dispose se propaga a través de la jerarquía de clases. No está destinado a tratar de evitar las malas prácticas de programación no administrada.
Por coincidencia acabo de publicar este comentario en otro lugar:
Una referencia a la retención incorrecta de un objeto sigue siendo una fuga de recursos. Esta es la razón por la que los programas de GC aún pueden tener fugas, generalmente debido al patrón de Observador: el observador está en una lista en lugar de observable y nunca se le quita. En última instancia, se necesita una
remove
para cadaadd
, al igual que unadelete
para cadanew
. Exactamente el mismo error de programación, causando exactamente el mismo problema. Un "recurso" es realmente un par de funciones que deben llamarse un número igual de veces con los argumentos correspondientes, y una "fuga de recursos" es lo que sucede cuando no lo hace.
Y dices:
El propósito de
IDisposable
es liberar cualquier recurso no administrado que controlen tus objetos.
Ahora, los operadores +=
y -=
en un evento son efectivamente un par de funciones a las que tiene que llamar un número igual de veces con los argumentos correspondientes (el par evento / controlador es los argumentos correspondientes).
Por lo tanto constituyen un recurso. Y como GC no los trata (o "gestiona") por usted, puede ser útil pensar en ellos como otro tipo de recurso no administrado. Como señala Jon Skeet en los comentarios, el no administrado generalmente tiene un significado específico, pero en el contexto de IDisposable
creo que es útil ampliarlo para incluir cualquier recurso similar a un recurso que deba ser "destruido" después de que se haya "construido". ".
Por lo tanto, la separación de eventos es un muy buen candidato para el manejo con IDisposable
.
Por supuesto, debe llamar a Dispose
algún lugar, y no necesita implementarlo en cada objeto (solo aquellos con relaciones de eventos que necesitan administración).
Además, tenga en cuenta que si un par de objetos están conectados por un evento, y los "arroja a la deriva", al perder todas las referencias a ellos en todos los demás objetos, no se mantienen vivos entre sí. GC no usa conteo de referencias. Una vez que un objeto (o isla de objetos) es inalcanzable, está listo para ser recolectado.
Solo tiene que preocuparse por los objetos inscritos como controladores de eventos con eventos en objetos que viven mucho tiempo. por ejemplo, un evento estático como AppDomain.UnhandledException
, o eventos en la ventana principal de su aplicación.
Pregúnteles si les gustaría verse obligados a apagar el motor después de usar una bicicleta, incluso si no hay un motor.
O si les gustaría ser obligados a presionar un botón de "apagar" en su silla, escritorio, taza de café y otras cosas antes de salir de su lugar de trabajo, incluso si no hay nada que apagar.
Implementar IDisposable obliga al usuario a decirle explícitamente al objeto cuando ya no se usa. Si este objeto no necesita limpiar nada, es solo una complejidad innecesaria.
Por cierto, los eventos IMHO que no se registran implementando IDisposable son una forma adecuada de limpiar eventos. Hay algo que "apagar".
Proponer una solución que sea muy superior a su solución ;-)
Por ejemplo, una alternativa simple podría ser usar WeakReference
s en los objetos WeakReference
para mantener las referencias a los controladores de eventos. Esto requeriría que se haga referencia a los manejadores de eventos en otro lugar siempre que se necesiten, pero tan pronto como salgan del alcance, se recolectan los residuos y las referencias débiles se pueden eliminar de los objetos de larga vida.
Un objeto debe implementarse como IDisponible si necesita asegurarse de que algo fuera de sí mismo que podría durar más tiempo se limpie antes de ser abandonado. Las propiedades de algunos objetos están "conectadas" a otros objetos, por lo que cambiar esas propiedades cambiará los otros objetos; a veces es necesario establecer esas propiedades en nulo. En vb.net, un campo "WithEvents" es realmente una propiedad que adjunta y separa a los controladores de eventos, y por lo tanto, los campos WithEvents en vb.net se deben establecer en Nothing. Tenga en cuenta que generalmente no es útil que un objeto implemente IDisposable con el único propósito de anular sus propios campos. Hay algunos casos en los que puede ser útil (por ejemplo, si un objeto que ha existido durante mucho tiempo contiene una referencia a un objeto creado recientemente, borrar la referencia puede permitir que el objeto creado más recientemente se recoja antes de lo que sería posible). de lo contrario) pero ciertamente no es necesario.
Lo que se necesita es asegurarse de que los objetos que necesitan limpiar otros objetos se implementen como IDisposibles y de que esos otros objetos se limpien. Estoy molesto con Microsoft por alentar a las personas a escribir código que abandona los controladores de eventos. Si bien es cierto que uno puede salirse con la suya abandonando los controladores de eventos en los casos en que los publicadores de eventos no sobreviven a los suscriptores, y estos casos ocurren comúnmente con elementos de GUI interconectados, realmente no veo ninguna razón por la que no siempre deba ser un evento. limpiado como una cuestión de curso
Una vez ayudé a un compañero de trabajo a resolver un problema similar con errores OutOfMemoryException (causados por eventos que dejaron referencias a objetos). Lo primero que hice fue ejecutar el código a través de FXCop, que resaltaba que no se había llamado a Dispose en una clase IDisposable.
La modificación de ese código para ser eliminado solucionó el problema. ¿Quizás deberías recomendar que se use FXCop?
Quizás encuentre el código con el problema en el repositorio de origen, ejecute FXCop en él; vea si resalta el problema (probablemente lo hará si es causado por una clase de .NET Framework) y utilícelo para convencer a sus compañeros de trabajo.
Indíquelos en la publicación de Joe Duffy sobre IDisposable / finalizadores : la sabiduría combinada de muchas personas inteligentes.
Actualmente me resulta difícil ver una declaración que diga "no lo implemente cuando no lo necesite", pero aparte de cualquier otra cosa, mostrarles la complejidad que implica implementarlo adecuadamente puede ayudar a disuadirlos. eso...
Desafortunadamente, si la gente no escucha, no escucha. Trate de hacer que expliquen por qué creen que necesitan IDisposable
. ¿Creen que el recolector de basura no funciona? Muéstrales que funciona. Si puede convencerlos de que no les sirve de nada (para la mayoría de los tipos), entonces seguramente dejarán de agregar trabajo por sí mismos ...
Como dice Brian, la implementación de IDisposable
no va a ayudar con el problema del evento por sí sola, sino que debe ser llamada por algo. Los finalizadores tampoco te van a ayudar en este caso. Realmente necesitan hacer algo explícitamente para eliminar los controladores de eventos.