how - idisposable implementation example c#
¿Debería "Descartar" solo usarse para tipos que contienen recursos no administrados? (15)
Entonces, mi pregunta es, si tienes un tipo que no contiene recursos no administrados, ¿vale la pena implementar IDisposable?
Cuando alguien coloca una interfaz IDisposable en un objeto, esto me dice que el creador tiene la intención de hacer algo en ese método o, en el futuro, pueden intentarlo. Siempre llamo a disponer en este caso solo para estar seguro. Incluso si no hace nada ahora mismo, podría ser en el futuro, y es malo obtener una pérdida de memoria porque actualizaron un objeto, y usted no llamó a Dispose cuando estaba escribiendo código la primera vez.
En verdad, es un juicio crítico. No desea aplicarlo en exceso, porque en ese momento, ¿por qué molestarse en tener un recolector de basura? ¿Por qué no simplemente deshacerse manualmente de cada objeto? Si existe la posibilidad de que deba deshacerse de los recursos no administrados, puede que no sea una mala idea. Todo depende, si las únicas personas que usan su objeto son las personas en su equipo, siempre puede seguir con ellos más tarde y decir: "Oye, esto necesita usar un recurso no administrado ahora. Tenemos que revisar el código y asegurarnos lo hemos arreglado ". Si publica esto para que otras organizaciones lo usen, eso es diferente. No hay una manera fácil de decirles a todos los que pudieron haber implementado ese objeto: "Oye, debes asegurarte de que esto esté dispuesto ahora". Déjame decirte que hay pocas cosas que enloquecen a las personas que actualizar un ensamblaje de terceros para descubrir que ellos fueron los que cambiaron su código e hicieron que tu aplicación se escapara de problemas de memoria.
Mi colega respondió que, bajo las sábanas, una conexión ADO.NET es un recurso administrado. Mi respuesta a su respuesta fue que todo en definitiva es un recurso no administrado.
Tiene razón, es un recurso administrado en este momento. ¿Alguna vez lo cambiarán? Quién sabe, pero no hace daño llamarlo. No intento hacer conjeturas sobre lo que hace el equipo de ADO.NET, así que si lo ponen y no hace nada, está bien. Aún así lo llamaré, porque una línea de código no afectará mi productividad.
También te encuentras con otro escenario. Supongamos que devuelve una conexión ADO.NET desde un método. Usted no sabe que la conexión ADO es el objeto base o un tipo derivado del bate. No sabe si esa implementación IDisposable de repente se volvió necesaria. Siempre lo llamo pase lo que pase, porque rastrear las pérdidas de memoria en un servidor de producción es una mierda cuando se cuelga cada 4 horas.
Hace poco tuve un debate con un colega sobre el valor de Dispose
y los tipos que implementan IDisposable
.
Creo que es valioso implementar IDisposable
para los tipos que deben limpiarse tan pronto como sea posible, incluso si no hay recursos no administrados para limpiar .
Mi colega piensa de manera diferente; No es necesario implementar IDisposable
si no tiene ningún recurso no administrado, ya que su tipo finalmente será basura.
Mi argumento era que si tenía una conexión ADO.NET que quería cerrar lo antes posible, implementar IDisposable
y using new MyThingWithAConnection()
tendría sentido. Mi colega respondió que, bajo las sábanas, una conexión ADO.NET es un recurso no administrado . Mi respuesta a su respuesta fue que todo en definitiva es un recurso no administrado .
Conozco el patrón desechable recomendado en el que liberas recursos administrados y no administrados si se llama Dispose
pero solo recursos no administrados si se llaman a través del finalizador / destructor (y escribí hace un momento sobre cómo alertar a los consumidores sobre el uso inapropiado de tus tipos IDisposable )
Entonces, mi pregunta es, si tienes un tipo que no contiene recursos no administrados, ¿vale la pena implementar IDisposable
?
todo en definitiva es un recurso no administrado.
No es verdad. Todo excepto la memoria utilizada por los objetos CLR que se gestiona (asigna y libera) solo por el marco.
Implementar IDisposable
y llamar a Dispose
en un objeto que no se aferra a ningún recurso no administrado (directa o indirectamente a través de objetos dependientes) no tiene sentido . No hace que la liberación de ese objeto sea determinista porque no puedes liberar directamente la memoria CLR del objeto por tu cuenta, ya que siempre es solo GC
quien lo hace. El objeto es un tipo de referencia porque los tipos de valores, cuando se usan directamente en un nivel de método, son asignados / liberados por las operaciones de la pila.
Ahora, todos afirman tener razón en sus respuestas. Déjame probar el mío. De acuerdo con la documentation :
El método Object.Finalize permite que un objeto intente liberar recursos y realizar otras operaciones de limpieza antes de ser recuperado por la recolección de basura.
En otras palabras, la memoria CLR del objeto se libera justo después de Object.Finalize()
. [nota: es posible omitir esta llamada explícitamente si es necesario]
Aquí hay una clase desechable sin recursos no administrados:
internal class Class1 : IDisposable
{
public Class1()
{
Console.WriteLine("Construct");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
~Class1()
{
Console.WriteLine("Destruct");
}
}
Tenga en cuenta que destructor llama implícitamente cada Finalize
en la cadena de herencia a Object.Finalize()
Y aquí está el método Main
de una aplicación de consola:
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Class1 obj = new Class1();
obj.Dispose();
}
Console.ReadKey();
}
Si llamar a Dispose
fuera una forma de liberar un objeto gestionado de forma determinista, cada "Dispose" sería seguido inmediatamente por un "Destruct", ¿no? Vea por usted mismo lo que sucede. Es muy interesante ejecutar esta aplicación desde una ventana de línea de comando.
Nota: Existe una forma de obligar a GC
a recopilar todos los objetos que están pendientes de finalización en el dominio de la aplicación actual, pero no para un solo objeto específico. Sin embargo, no necesita llamar a Dispose
para tener un objeto en la cola de finalización. Se desaconseja encarecidamente forzar la recopilación, ya que es probable que perjudique el rendimiento general de la aplicación.
EDITAR
Hay una excepción: la administración estatal. Dispose
puede manejar el cambio de estado si su objeto administra un estado externo. Incluso si el estado no es un objeto no administrado, es muy conveniente usarlo como tal debido al tratamiento especial que tiene IDisposable
. Ejemplo sería un contexto de seguridad o contexto de suplantación.
using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
// do something as SomeUser
}
// back to your user
No es el mejor ejemplo porque WindowsImpersonationContext
usa el identificador del sistema internamente pero se obtiene la imagen.
En IDisposable
cuando implemente IDisposable
debe tener (o planear tener) algo significativo que hacer en el método Dispose
. De lo contrario, es solo una pérdida de tiempo. IDisposable
no cambia la forma en que GC administra su objeto.
Creo que es más útil pensar en IDisposable
en términos de responsabilidades . Un objeto debe implementar IDisposable
si sabe algo que deberá hacerse entre el momento en que ya no es necesario y el final del universo (y preferiblemente tan pronto como sea posible), y si es el único objeto con la información y el ímpetu para hacerlo. Un objeto que abre un archivo, por ejemplo, tendría la responsabilidad de asegurarse de que el archivo se cierre. Si el objeto simplemente desapareciera sin cerrar el archivo, es posible que el archivo no se cierre en un plazo razonable.
Es importante tener en cuenta que incluso los objetos que solo interactúan con objetos administrados al 100% pueden hacer cosas que deben ser limpiadas (y deberían usar IDisposable
). Por ejemplo, un IEnumerator
que se adjunta al evento "modificado" de una colección deberá separarse cuando ya no sea necesario. De lo contrario, a menos que el enumerador utilice algún truco complejo, el enumerador nunca será recolectado como basura siempre que la colección esté dentro del alcance. Si la colección se enumera un millón de veces, un millón de enumeradores se adjuntará a su controlador de eventos.
Tenga en cuenta que, a veces, es posible utilizar finalizadores para la limpieza en los casos en que, por cualquier motivo, un objeto se abandona sin que se haya invocado primero Dispose
. Algunas veces esto funciona bien; Someitmes funciona muy mal. Por ejemplo, aunque Microsoft.VisualBasic.Collection
utiliza un finalizador para separar los enumeradores de los eventos "modificados", intentar enumerar dicho objeto miles de veces sin un Dispose
o recolección de basura intermedias hará que se vuelva muy lento: muchas órdenes de magnitud más lenta que el rendimiento que se obtendría si se usa Dispose
correctamente.
En uno de mis proyectos, tuve una clase con hilos gestionados dentro, los llamaremos hilo A, hilo B y un objeto IDisposable, lo llamaremos C.
A solía eliminar C al salir. B solía usar C para guardar excepciones.
Mi clase tuvo que implementar IDisposable y un descrtuctor para garantizar que las cosas se eliminen en el orden correcto. Sí, el GC podría limpiar mis artículos, pero mi experiencia fue que había una condición de carrera a menos que logre la limpieza de mi clase.
Existen diferentes usos válidos para IDisposable
. Un ejemplo simple es la celebración de un archivo abierto, que debe estar cerrado en determinado momento, tan pronto como ya no lo necesite. Por supuesto, podría proporcionar un método Close
, pero tenerlo en Dispose
y usar pattern como using (var f = new MyFile(path)) { /*process it*/ }
sería más seguro.
Un ejemplo más popular sería tener algunos otros recursos IDisposable
, lo que generalmente significa que debe proporcionar su propio Dispose
para poder eliminarlos también.
En general, tan pronto como quiera tener una destrucción determinista de cualquier cosa, debe implementar IDisposable
.
La diferencia entre mi opinión y la tuya es que implemento IDisposable
tan pronto como algún recurso necesite destrucción / liberación determinista , no necesariamente tan pronto como sea posible . Depender de la recolección de basura no es una opción en este caso (contrariamente a lo que afirma su colega), porque ocurre en un momento impredecible y, de hecho, puede que no suceda en absoluto.
El hecho de que cualquier recurso no se gestione bajo la cubierta realmente no significa nada: el desarrollador debe pensar en términos de "cuándo y cómo es correcto deshacerse de este objeto" en lugar de "cómo funciona debajo de la cubierta". La implementación subyacente puede cambiar con el tiempo de todos modos.
De hecho, una de las principales diferencias entre C # y C ++ es la ausencia de destrucción determinista predeterminada. El IDisposable
viene a cerrar la brecha: puede ordenar la destrucción determinista (aunque no puede asegurarse de que los clientes la llamen, de la misma manera en C ++ no puede estar seguro de que los clientes invoquen delete
en el objeto).
Pequeña adición: ¿cuál es la diferencia entre la liberación determinista de los recursos y la liberación de los mismos tan pronto como sea posible ? En realidad, esas son nociones diferentes (aunque no completamente ortogonales).
Si los recursos deben liberarse de manera determinista , esto significa que el código del cliente debería tener la posibilidad de decir "Ahora, quiero liberar este recurso". En realidad, este no es el momento más temprano posible en que se puede liberar el recurso: el objeto que contiene el recurso podría haber obtenido todo lo que necesita del recurso, por lo que potencialmente podría liberar el recurso. Por otro lado, el objeto puede optar por mantener el recurso (generalmente no administrado) incluso después de que se haya ejecutado el Dispose
del objeto, limpiándolo solo en el finalizador (si mantener el recurso durante demasiado tiempo no representa ningún problema).
Por lo tanto, para liberar el recurso lo más pronto posible , estrictamente hablando, Dispose
no es necesario: el objeto puede liberar el recurso tan pronto como se dé cuenta de que el recurso ya no se necesita. Sin embargo, Dispose
sirve como una pista útil de que el objeto ya no es necesario, por lo que tal vez los recursos se puedan liberar en ese momento, si corresponde.
Una adición más necesaria: ¡no solo los recursos no administrados necesitan una desasignación determinista! Este parece ser uno de los puntos clave de la diferencia de opiniones entre las respuestas a esta pregunta. Uno puede tener una construcción puramente imaginativa, que puede necesitar ser liberada de manera determinista.
Algunos ejemplos son: derecho a acceder a una estructura compartida (piense en RW-lock ), una gran cantidad de memoria (imagínese que está administrando manualmente parte de la memoria del programa), una licencia para usar algún otro programa (imagínese que no tiene permitido ejecutar más de X copias de algún programa simultáneamente), etc. Aquí el objeto a liberar no es un recurso no administrado, sino un derecho a hacer / usar algo, que es una construcción puramente interna de la lógica de su programa.
Pequeña adición: aquí hay una pequeña lista de ejemplos claros de [ab] usando IDisposable
: http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable .
No son recursos necesarios en absoluto (administrados o no). A menudo, IDisposable
es solo una forma conveniente de eliminar combersome try {..} finally {..}
, simplemente compare:
Cursor savedCursor = Cursor.Current;
try {
Cursor.Current = Cursors.WaitCursor;
SomeLongOperation();
}
finally {
Cursor.Current = savedCursor;
}
con
using (new WaitCursor()) {
SomeLongOperation();
}
donde WaitCursor
es IDisposable
para ser adecuado para using
:
public sealed class WaitCursor: IDisposable {
private Cursor m_Saved;
public Boolean Disposed {
get;
private set;
}
public WaitCursor() {
Cursor m_Saved = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}
public void Dispose() {
if (!Disposed) {
Disposed = true;
Cursor.Current = m_Saved;
}
}
}
Puedes combinar fácilmente tales clases:
using (new WaitCursor()) {
using (new RegisterServerLongOperation("My Long DB Operation")) {
SomeLongRdbmsOperation();
}
SomeLongOperation();
}
No, no es solo para recursos no administrados.
Se sugiere como un mecanismo integrado de limpieza básica llamado por framework, que le permite la posibilidad de limpiar cualquier recurso que desee, pero lo mejor es que la administración de recursos no sea administrada naturalmente.
Respuesta corta: Absolutamente NO. Si su tipo tiene miembros administrados o no administrados, debe implementar IDisposable.
Ahora detalles: respondí esta pregunta y proporcioné muchos más detalles sobre el funcionamiento interno de la administración de memoria y el GC sobre preguntas aquí en . Aquí hay solo algunos:
- ¿Es una mala práctica depender del recolector de basura automatizado .NET?
- ¿Qué sucede si no llamo Dispose en el objeto de lápiz?
- Eliminar, ¿cuándo se llama?
En cuanto a las mejores prácticas sobre la implementación de IDisposable, consulte la publicación de mi blog:
Si agrega s IDisposable
, entonces debe implementar la interfaz para que esos miembros se limpien de manera oportuna. ¿De qué otra manera se myConn.Dispose()
en el ejemplo de conexión ADO.Net que usted cita?
Sin embargo, no creo que sea correcto decir que todo es un recurso no administrado en este contexto . Tampoco estoy de acuerdo con tu colega.
Si bien hay buenas respuestas para esto, solo quería hacer algo explícito.
Hay tres casos para implementar IDisposable
:
- Está utilizando recursos no administrados directamente. Esto normalmente implica recuperar un
IntPrt
u otro tipo de identificador de una llamada P / Invoke que debe ser liberado por una llamada P / Invoke diferente - Está utilizando otros objetos
IDisposable
y debe ser responsable de su disposición - Tiene alguna otra necesidad o uso, incluida la comodidad del bloque de
using
.
Aunque podría ser un poco parcial, realmente debería leer (y mostrarle a su colega) el Wiki de en IDisposable
.
Su tipo debe implementar IDisposable si hace referencia a recursos no administrados o si contiene referencias a objetos que implementan IDisposable.
Tenga en cuenta que los recursos no administrados pueden incluir objetos CLR estándar, por ejemplo, almacenados en algunos campos estáticos, todos ejecutados en modo seguro sin importaciones no administradas.
No hay una manera simple de decir si una clase determinada que implementa IDiposable
realmente necesita limpiar algo. Mi regla de oro es llamar siempre a Dispose
sobre objetos que no conozco demasiado bien, como una biblioteca de terceros.
Tienes razón. Las conexiones de bases de datos administradas, los archivos, las claves de registro, los sockets, etc. se aferran a los objetos no administrados. Es por eso que implementan IDisposable
. Si su tipo posee objetos desechables, debe implementar IDisposable
y disponerlos en su método Dispose
. De lo contrario, pueden permanecer vivos hasta que se recoja la basura, lo que da como resultado archivos bloqueados y otros comportamientos inesperados.
Dispose
debe usar para cualquier recurso con una vida útil limitada . Se debe usar un finalizador para cualquier recurso no administrado . Cualquier recurso no administrado debe tener una vida útil limitada, pero hay muchos recursos administrados (como bloqueos) que también tienen vidas limitadas.
Implementar IDisposable
si el objeto posee objetos no administrados o cualquier objeto desechable gestionado
Si un objeto utiliza recursos no administrados, debe implementar IDisposable
. El objeto que posee un objeto desechable debe implementar IDisposable
para garantizar que se liberen los recursos subyacentes no administrados. Si se sigue la regla / convención, es lógico concluir que no eliminar objetos desechables gestionados equivale a no liberar recursos no gestionados.