C # 2008
He estado trabajando en esto por un tiempo, y todavía estoy confundido sobre el uso de los métodos de finalizar y desechar en el código. Mis preguntas están abajo:
Sé que solo necesitamos un finalizador mientras desechamos recursos no administrados. Sin embargo, si hay recursos administrados que hacen llamadas a recursos no administrados, ¿aún necesitaría implementar un finalizador?
Sin embargo, si desarrollo una clase que no usa ningún recurso no administrado, directa o indirectamente, ¿debo implementar el
IDisposable
para permitir que los clientes de esa clase usen la 'declaración de uso'?¿Sería factible implementar IDisposable solo para permitir que los clientes de su clase utilicen la instrucción using?
using(myClass objClass = new myClass()) { // Do stuff here }
He desarrollado este simple código a continuación para demostrar el uso Finalizar / eliminar:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
Pregunta sobre el código fuente:
Aquí no he agregado el finalizador, y normalmente el GC llamará al finalizador, y el finalizador llamará a Dispose. Como no tengo el finalizador, ¿cuándo llamo al método Dispose? ¿Es el cliente de la clase quien debe llamarlo?
Entonces, mi clase en el ejemplo se llama NoGateway y el cliente podría usar y deshacerse de la clase de esta manera:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
¿Se llamará automáticamente al método Dispose cuando la ejecución llegue al final del bloque de uso, o el cliente tiene que llamar manualmente al método dispose? es decir
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
Estoy usando la
WebClient
clase en miNoGateway
clase. Debido a queWebClient
implementa laIDisposable
interfaz, ¿significa esto queWebClient
indirectamente usa recursos no administrados? ¿Existe una regla difícil y rápida para seguir esto? ¿Cómo sé que una clase usa recursos no administrados?
fuente
Respuestas:
El patrón IDisposable recomendado está aquí . Al programar una clase que usa IDisposable, generalmente debe usar dos patrones:
Al implementar una clase sellada que no utiliza recursos no administrados, simplemente implementa un método Dispose como con las implementaciones de interfaz normales:
Cuando implemente una clase sin sellar, hágalo así:
Tenga en cuenta que no he declarado un finalizador en
B
; solo debe implementar un finalizador si tiene recursos reales no administrados para eliminar. El CLR trata los objetos finalizables de manera diferente a los objetos no finalizables, incluso siSuppressFinalize
se llama.Por lo tanto, no debe declarar un finalizador a menos que sea necesario, pero le da a los herederos de su clase un gancho para que lo llamen
Dispose
e implementen un finalizador si usan recursos no administrados directamente:Si no está utilizando recursos no administrados directamente (
SafeHandle
y los amigos no cuentan, ya que declaran sus propios finalizadores), no implemente un finalizador, ya que el GC trata las clases finalizables de manera diferente, incluso si luego suprime el finalizador. También tenga en cuenta que, aunqueB
no tiene un finalizador, todavía llamaSuppressFinalize
para tratar correctamente cualquier subclase que implemente un finalizador.Cuando una clase implementa la interfaz IDisposable, significa que en algún lugar hay algunos recursos no administrados que deben eliminarse cuando haya terminado de usar la clase. Los recursos reales están encapsulados dentro de las clases; no necesita eliminarlos explícitamente. Simplemente llamando
Dispose()
o ajustando la clase en unausing(...) {}
se asegurará de que los recursos no administrados se eliminen según sea necesario.fuente
IDisposable
, lo más probable es que se quede por un tiempo de todos modos. Estás ahorrando al CLR el esfuerzo de tener que copiarlo de Gen0 -> Gen1 -> Gen2El patrón oficial para implementar
IDisposable
es difícil de entender. Creo que este es mejor :Una solución aún mejor es tener una regla de que siempre debe crear una clase de contenedor para cualquier recurso no administrado que necesite manejar:
Con
SafeHandle
y sus derivados, estas clases deberían ser muy raras .El resultado para las clases desechables que no tratan directamente con recursos no administrados, incluso en presencia de herencia, es poderoso: ya no necesitan preocuparse por los recursos no administrados . Serán fáciles de implementar y comprender:
fuente
disposed
bandera y marcar en consecuencia.disposed
bandera, verificarla antes de eliminarla y configurarla después de eliminarla. Mira aquí la idea. También debe verificar la bandera antes de cualquier método de la clase. ¿Tiene sentido? ¿Es complicado?Tenga en cuenta que cualquier implementación IDisposable debe seguir el siguiente patrón (en mi humilde opinión). Desarrollé este patrón basado en información de varios "dioses" excelentes de .NET, las Directrices de diseño de .NET Framework (tenga en cuenta que MSDN no sigue esto por alguna razón). Las Directrices de diseño de .NET Framework fueron escritas por Krzysztof Cwalina (Arquitecto CLR en ese momento) y Brad Abrams (Creo que el Administrador del programa CLR en ese momento) y Bill Wagner ([C # efectivo] y [C # más efectivo] (solo tome un búsquelos en Amazon.com:
Tenga en cuenta que NUNCA debe implementar un Finalizador a menos que su clase contenga directamente (no herede) recursos NO administrados. Una vez que implemente un Finalizador en una clase, incluso si nunca se llama, se garantiza que vivirá para una colección adicional. Se coloca automáticamente en la Cola de finalización (que se ejecuta en un solo hilo). Además, una nota muy importante ... ¡todo el código ejecutado dentro de un Finalizador (si necesita implementar uno) DEBE ser seguro para subprocesos y para excepciones! MALAS cosas sucederán de otra manera ... (es decir, comportamiento indeterminado y, en el caso de una excepción, un bloqueo fatal irrecuperable de la aplicación).
El patrón que he reunido (y he escrito un fragmento de código) es el siguiente:
Aquí está el código para implementar IDisposable en una clase derivada. Tenga en cuenta que no necesita enumerar explícitamente la herencia de IDisposable en la definición de la clase derivada.
He publicado esta implementación en mi blog en: Cómo implementar correctamente el patrón de eliminación
fuente
Interlocked.Exchange
enteroIsDisposed
en la función de envoltura no virtual sería más seguro.Estoy de acuerdo con pm100 (y debería haberlo dicho explícitamente en mi publicación anterior).
Nunca debe implementar IDisposable en una clase a menos que lo necesite. Para ser muy específico, hay alrededor de 5 veces en las que necesitaría / debería implementar IDisposable:
Su clase contiene explícitamente (es decir, no a través de la herencia) cualquier recurso administrado que implemente IDisposable y debe limpiarse una vez que su clase ya no se use. Por ejemplo, si su clase contiene una instancia de Stream, DbCommand, DataTable, etc.
Su clase contiene explícitamente cualquier recurso administrado que implemente un método Close (), por ejemplo, IDataReader, IDbConnection, etc. Tenga en cuenta que algunas de estas clases implementan IDisposable al tener Dispose () y un método Close ().
Su clase contiene explícitamente un recurso no administrado, por ejemplo, un objeto COM, punteros (sí, puede usar punteros en C # administrado, pero deben declararse en bloques "inseguros", etc. En el caso de recursos no administrados, también debe asegurarse de llame a System.Runtime.InteropServices.Marshal.ReleaseComObject () en el RCW. Aunque el RCW es, en teoría, un contenedor administrado, todavía hay un recuento de referencias debajo de las cubiertas.
Si su clase se suscribe a eventos usando referencias fuertes. Debe anular el registro / separarse de los eventos. ¡Siempre asegúrese de que estos no sean nulos antes de intentar anular el registro / separarlos!
Su clase contiene cualquier combinación de lo anterior ...
Una alternativa recomendada para trabajar con objetos COM y tener que usar Marshal.ReleaseComObject () es usar la clase System.Runtime.InteropServices.SafeHandle.
El BCL (Base Class Library Team) tiene una buena publicación de blog al respecto aquí http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Una nota muy importante es que si está trabajando con WCF y limpiando recursos, SIEMPRE debe evitar el bloque 'usar'. Hay muchas publicaciones de blog y algunas en MSDN sobre por qué es una mala idea. También he publicado sobre esto aquí: no use 'using ()' con un proxy WCF
fuente
Usando lambdas en lugar de IDisposable.
Nunca me ha encantado la idea de usar / IDisposable. El problema es que requiere que la persona que llama:
Mi nuevo método preferido es usar un método de fábrica y una lambda en su lugar
Imagine que quiero hacer algo con un SqlConnection (algo que debería envolverse en un uso). Clásicamente harías
Nueva manera
En el primer caso, la persona que llama simplemente no puede usar la sintaxis de uso. En el segundo caso, el usuario no tiene otra opción. No hay ningún método que cree un objeto SqlConnection, la persona que llama debe invocar DoWithConnection.
DoWithConnection se ve así
MakeConnection
ahora es privadofuente
DoForAll(Action<T>) where T:IComparable<T>
, llamando al delegado indicado en cada registro. Dados dos de estos objetos, los cuales devolverán datos en orden ordenado, ¿cómo generaría uno todos los elementos que existen en una colección pero no en el otro? Si los tipos implementadosIEnumerable<T>
, uno podría realizar una operación de fusión, pero eso no funcionaráDoForAll
.DoForAll
colecciones sin tener que copiar primero una, en su totalidad, en alguna otra estructura, sería usar dos hilos, que serían recursos bastante más pesados que simplemente usar un par de IEnumerable y tener cuidado para liberarlos.nadie respondió a la pregunta sobre si debe implementar IDisposable aunque no lo necesite.
Respuesta corta: no
Respuesta larga:
Esto permitiría a un consumidor de su clase usar 'using'. La pregunta que haría es: ¿por qué lo harían? La mayoría de los desarrolladores no usarán 'usar' a menos que sepan que deben hacerlo, y cómo lo saben. Ya sea
Entonces, al implementar IDisposable, le está diciendo a los desarrolladores (al menos algunos) que esta clase concluye algo que debe ser liberado. Usarán 'usar', pero hay otros casos en los que no es posible usar (el alcance del objeto no es local); y tendrán que empezar a preocuparse por la vida útil de los objetos en esos otros casos, me preocuparía con seguridad. Pero esto no es necesario
Implementa Idisposable para permitirles usar el uso, pero no usarán el uso a menos que se lo indique.
Así que no lo hagas
fuente
Si está utilizando otros objetos administrados que utilizan recursos no administrados, no es su responsabilidad asegurarse de que estén finalizados. Su responsabilidad es llamar a Dispose en esos objetos cuando se llama a Dispose en su objeto y se detiene allí.
Si su clase no utiliza recursos escasos, no entiendo por qué haría que su clase implemente IDisposable. Solo debes hacerlo si eres:
Sí, el código que usa su código debe llamar al método Dispose de su objeto. Y sí, el código que usa su objeto puede usar
using
como lo ha mostrado.(¿2 nuevamente?) Es probable que el WebClient utilice recursos no administrados u otros recursos administrados que implementan IDisposable. La razón exacta, sin embargo, no es importante. Lo importante es que implemente IDisposable, por lo que le corresponde actuar de acuerdo con ese conocimiento al deshacerse del objeto cuando haya terminado con él, incluso si resulta que WebClient no utiliza ningún otro recurso.
fuente
Eliminar patrón:
Ejemplo de herencia:
fuente
Algunos aspectos de otra respuesta son ligeramente incorrectos por 2 razones:
Primero,
en realidad es equivalente a:
Esto puede sonar ridículo ya que el operador 'nuevo' nunca debe devolver 'nulo' a menos que tenga una excepción OutOfMemory. Pero considere los siguientes casos: 1. Llame a FactoryClass que devuelve un recurso IDisposable o 2. Si tiene un tipo que puede heredar o no de IDisposable dependiendo de su implementación, recuerde que he visto que el patrón IDisposable se implementó incorrectamente en muchos veces en muchos clientes donde los desarrolladores simplemente agregan un método Dispose () sin heredar de IDisposable (bad, bad, bad). También podría tener el caso de un recurso IDisposable que se devuelve desde una propiedad o método (nuevamente malo, malo, malo, no regale sus recursos IDisposable)
Si el operador 'as' devuelve nulo (o propiedad o método que devuelve el recurso), y su código en el bloque 'usar' protege contra 'nulo', su código no explotará cuando intente llamar a Dispose en un objeto nulo debido a el cheque nulo 'incorporado'.
La segunda razón por la cual su respuesta no es precisa es por lo siguiente:
Primero, la finalización (al igual que la propia GC) no es determinista. El CLR determina cuándo llamará a un finalizador. es decir, el desarrollador / código no tiene idea. Si el patrón IDisposable se implementa correctamente (como he publicado anteriormente) y se ha llamado GC.SuppressFinalize (), NO se llamará al Finalizador. Esta es una de las grandes razones para implementar correctamente el patrón correctamente. Dado que solo hay 1 subproceso Finalizador por proceso administrado, independientemente del número de procesadores lógicos, puede degradar fácilmente el rendimiento haciendo una copia de seguridad o incluso colgando el subproceso Finalizador olvidando llamar a GC.SuppressFinalize ().
He publicado una implementación correcta del Patrón de eliminación en mi blog: Cómo implementar correctamente el Patrón de eliminación
fuente
NoGateway = new NoGateway();
yNoGateway != null
?1) WebClient es un tipo administrado, por lo que no necesita un finalizador. El finalizador es necesario en el caso de que sus usuarios no eliminen () de su clase NoGateway y el tipo nativo (que no es recolectado por el GC) deba limpiarse después. En este caso, si el usuario no llama a Dispose (), el GC eliminará el WebClient contenido inmediatamente después de que NoGateway lo haga.
2) Indirectamente sí, pero no debería preocuparse por eso. Su código es correcto como está y no puede evitar que sus usuarios se olviden de Eliminar () muy fácilmente.
fuente
Patrón de msdn
fuente
es equivalente a
Se llama a un finalizador para que el GC destruya su objeto. Esto puede ser en un momento totalmente diferente que cuando abandonas tu método. Dispose of IDisposable se llama inmediatamente después de que abandone el bloque de uso. Por lo tanto, el patrón generalmente se usa para liberar recursos inmediatamente después de que ya no los necesite.
fuente
Por lo que sé, es muy recomendable NO usar el Finalizador / Destructor:
Principalmente, esto se debe a que no se sabe cuándo o SI se llamará. El método de eliminación es mucho mejor, especialmente si usted usa o elimina directamente.
usar es bueno. úsalo :)
fuente