c# - pattern - override void dispose
¿Estoy implementando IDisposable correctamente? (7)
Debe comprobar que _Writer
no es null
antes de intentar eliminarlo. (No parece probable que alguna vez sea null
, ¡pero por si acaso!)
protected virtual void Dispose(bool disposing)
{
if (!_Disposed)
{
if (disposing && (_Writer != null))
{
_Writer.Dispose();
}
_Writer = null;
_Disposed = true;
}
}
Esta clase utiliza un StreamWriter
y, por lo tanto, implementa IDisposable
.
public class Foo : IDisposable
{
private StreamWriter _Writer;
public Foo (String path)
{
// here happens something along the lines of:
FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
_Writer = new StreamWriter (fileWrite, new ASCIIEncoding ());
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
~Foo()
{
Dispose (false);
}
protected virtual void Dispose (bool disposing)
{
if (_Disposed) {
return;
}
if (disposing) {
_Writer.Dispose ();
}
_Writer = null;
_Disposed = true;
}
private bool _Disposed;
}
}
¿Hay algún problema con la implementación actual? Es decir, ¿tengo que liberar manualmente el FileStream
subyacente? ¿Está Dispose(bool)
escrito correctamente?
Estoy de acuerdo con todo lo dicho en los otros comentarios, pero también señalaría eso;
En realidad, no es necesario establecer _Writer = null en ninguno de los casos.
Si lo va a hacer, probablemente sea mejor colocarlo en el lugar donde está la disposición. Probablemente no haga una gran diferencia, pero generalmente no se supone que juegues con objetos gestionados cuando el finalizador los elimine (que de todos modos no necesitas en este caso, como han señalado otros).
La práctica recomendada es usar un finalizador solo cuando tiene recursos no administrados (como los manejadores de archivos nativos, punteros de memoria, etc.).
Aunque tengo dos pequeñas sugerencias,
No es necesario tener la variable " m_Disposed
" para probar si previamente ha llamado a Dispose
en sus recursos administrados. Podrías usar código similar como eso,
protected virtual void Dispose (bool disposing) { if (disposing) { if (_Writer != null) _Writer.Dispose (); } _Writer = null; }
Abra los archivos solo el tiempo que sea necesario. Entonces, en su ejemplo, probaría la existencia del archivo en el constructor utilizando File.Exists
y luego, cuando necesite leer / escribir el archivo, lo abrirá y lo usará.
Además, si simplemente desea escribir texto en un archivo, eche un vistazo a File.WriteAllText
o File.OpenText
o incluso a File.AppendText
que está dirigido a archivos de texto específicamente con ASCIIEncoding
.
Aparte de eso, sí, está implementando el patrón de disposición de .NET correctamente.
No necesita un método Finalize
(destructor) ya que no tiene ningún objeto no administrado. Sin embargo, debe mantener la llamada a GC.SuppressFinalize
en caso de que una clase GC.SuppressFinalize
de Foo tenga objetos no administrados y, por lo tanto, un finalizador.
Si sella la clase, sabrá que los objetos no administrados nunca entrarán en la ecuación, por lo que no es necesario agregar la sobrecarga protected virtual Dispose(bool)
, o el GC.SuppressFinalize
.
Editar:
La objeción de @ AnthonyWJones a esto es que si sabe que las subclases no harán referencia a objetos no administrados, no es necesario GC.SuppressFinalize
Dispose(bool)
y GC.SuppressFinalize
su GC.SuppressFinalize
. Pero si este es el caso, realmente deberías hacer las clases internal
lugar de public
, y el método Dispose()
debería ser virtual
. Si sabe lo que está haciendo, siéntase libre de no seguir el patrón sugerido por Microsoft, ¡pero debe conocer y entender las reglas antes de romperlas!
No necesita usar esta versión extensa de implementación IDisposable si su clase no usa directamente recursos no administrados.
Un simple
public virtual void Dispose()
{
_Writer.Dispose();
}
Será suficiente.
Si su consumidor no puede Desechar su objeto, será GC''d normalmente sin una llamada a Dispose, el objeto que mantiene _Writer también tendrá GC''d y tendrá un finalizador, por lo que aún puede limpiar sus recursos no administrados adecuadamente.
Editar
Después de haber investigado los enlaces proporcionados por Matt y otros, he llegado a la conclusión de que mi respuesta es válida . Aquí es por qué: -
La premisa detrás del "patrón" de implementación desechable (me refiero a la virtual protegida Dispose (bool), SuppressFinalize, etc. marlarky) en una clase heredable es que una subclase puede aferrarse a un recurso no administrado.
Sin embargo, en el mundo real, la gran mayoría de nosotros los desarrolladores de .NET nunca vamos a ninguna parte cerca de un recurso no administrado. Si tuviera que cuantificar el " poder " por encima de la cifra de probabilidad que se le ocurriría para la clase de codificación .NET?
Supongamos que tengo un tipo de persona (que, por el bien del argumento, tiene un tipo desechable en uno de sus campos y, por lo tanto, debería ser desechable). Ahora tengo clientes heredados, empleados, etc. ¿Es realmente razonable para mí saturar la clase Persona con este "Patrón" en caso de que alguien herede Persona y quiera tener un recurso no administrado?
A veces, los desarrolladores pueden complicar las cosas en un intento de codificar todas las circunstancias posibles sin usar un poco de sentido común con respecto a la probabilidad relativa de tales circunstancias.
Si alguna vez quisiéramos usar un recurso no administrado directamente, el patrón sensible sería envolver una cosa así en su propia clase donde el "patrón desechable" completo sería razonable. Por lo tanto, en el cuerpo significativamente grande del código "normal" no debemos tener que preocuparnos por todo ese desperdicio. Si necesitamos IDisposable, podemos usar el patrón simple de arriba, heredable o no.
Uf, me alegro de sacarlo de mi pecho. ;)
Si abres StreamWriter, también debes desecharlo o tendrás una fuga.
Tengo muchas clases como esta, y mi recomendación es sellar la clase siempre que sea posible. IDisposable
herencia IDisposable
+ puede funcionar, pero el 99% de las veces no la necesita, y es fácil cometer errores.
A menos que esté escribiendo una API pública (en cuyo caso, es bueno permitir que las personas implementen su código como lo deseen, es decir, usar IDisposable
), no es necesario que lo respalde.
solo haz:
public sealed class Foo : IDisposable
{
private StreamWriter _Writer;
public Foo(string path)
{
FileStream fileWrite = File.Open (path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
try {
_Writer = new StreamWriter (fileWrite, new ASCIIEncoding());
} catch {
fileWrite.Dispose();
throw;
}
}
public void Dispose()
{
_Writer.Dispose();
}
}
Tenga en cuenta el intento ... captura; técnicamente, el constructor de StreamWriter
podría lanzar una excepción, en cuyo caso nunca toma posesión de FileStream
y usted mismo debe disponer de él.
Si realmente está usando muchos IDisposable
, considere colocar ese código en C ++ / CLI: eso hace que todo ese código esté listo para usted (usa de manera transparente la tecnología de destrucción determinística apropiada para los objetos nativos y los objetos administrados).
Wikipedia tiene una muestra decente IDisposable para C ++ (en realidad, si tiene muchos IDisposable, C ++ es en realidad mucho más simple que C #): Wikipedia: Finalizadores C ++ / CLI y variables automáticas .
Por ejemplo, implementar un contenedor desechable "seguro" en C ++ / CLI parece ...
public ref class MyDisposableContainer
{
auto_handle<IDisposable> kidObj;
auto_handle<IDisposable> kidObj2;
public:
MyDisposableContainer(IDisposable^ a,IDisposable^ b)
: kidObj(a), kidObj2(b)
{
Console::WriteLine("look ma, no destructor!");
}
};
Este código eliminará correctamente kidObj y kidObj2 incluso sin agregar una implementación IDisposable personalizada, y es robusto a las excepciones en cualquiera de las implementaciones de Dispose (no es que deban ocurrir, pero aún así), y es fácil de mantener frente a muchos más miembros IDisposable
o nativos recursos
No es que sea un gran fanático de C ++ / CLI, pero para el código altamente orientado a los recursos tiene C # beat fácilmente, y tiene una interoperación absolutamente brillante tanto con el código administrado como con el nativo, en resumen, el código de pegamento perfecto ;-). Tiendo a escribir el 90% de mi código en C #, pero uso C ++ / CLI para todas las necesidades de interoperabilidad (especialmente si desea llamar a cualquier función de win32 - MarshalAs y otros atributos de interoperabilidad en todo el lugar son horribles e incomprensibles).