Implementación correcta de IDisposable
La interfaz utilizada para liberar recursos de objetos en .NET es IDisposable, pero la implementación de esta interfaz es bastante particular. Además, hay dos funciones: Finalizar y Cerrar.
MSDN recomienda implementar la interfaz IDisposable según el siguiente patrón:
1 clase pública Foo: IDisposable
2 {
3 eliminación pública vacía ()
4 {
5 Eliminar (verdadero);
6 GC.SuppressFinalize(esto);
7}
8
9 eliminación de vacío virtual protegido (eliminación bool)
10 {
11 si (!m_disposed)
12 {
13 si (desechando)
14 {
15 // Liberar recursos administrados
16}
17
18 // Liberar recursos no administrados
19
20 m_disposed = verdadero;
veintiuno }
Veintidós }
veintitrés
24 ~Foo()
25 {
26 Eliminar (falso);
27}
28
29 bool privado m_disposed;
30}
31
32
En realidad, existen dos funciones para liberar recursos en objetos .NET: Eliminar y Finalizar. El propósito de Finalize es liberar recursos no administrados, mientras que Dispose se usa para liberar todos los recursos, incluidos los administrados y no administrados.
En este modo, la función void Dispose(bool disposing) utiliza un parámetro de eliminación para distinguir si Dispose() la está llamando actualmente. Si Dispose() lo llama, los recursos administrados y no administrados deben liberarse al mismo tiempo. Si lo llama ~Foo() (es decir, Finalize() de C#), entonces solo necesita liberar los recursos no administrados.
Esto se debe a que otro código llama explícitamente a la función Dispose() y requiere la liberación de recursos, mientras que el GC llama a Finalize. Es posible que no sea necesario destruir otros objetos administrados a los que hace referencia Foo cuando se llama al GC, e incluso si se destruyen, serán llamados por el GC. Por lo tanto, en Finalizar sólo es necesario liberar los recursos no administrados. Por otro lado, dado que los recursos administrados y no administrados se han liberado en Dispose (), no es necesario volver a llamar a Finalize cuando GC recicla el objeto, así que llame a GC.SuppressFinalize (this) en Dispose () para evitar la duplicación. Finalizar.
Sin embargo, no hay problema incluso si se llama repetidamente a Finalize y Dispose, debido a la existencia de la variable m_disposed, el recurso solo se liberará una vez y las llamadas redundantes se ignorarán.
Por tanto, el patrón anterior garantiza:
1. Finalizar solo libera recursos no administrados;
2. Dispose libera recursos administrados y no administrados.
3. No hay problema en llamar a Finalize y Dispose repetidamente.
4. Finalize y Dispose comparten la misma estrategia de liberación de recursos, por lo que no hay conflicto entre ellos; .
En C#, este patrón debe implementarse explícitamente, donde la función ~Foo() de C# representa Finalize(). En C++/CLI, este modo se implementa automáticamente, pero el destructor de clases de C++ es diferente.
Según la semántica de C++, se llama al destructor cuando sale del alcance o se elimina. En C++ administrado (es decir, C++ administrado en .NET 1.1), el destructor es equivalente al método Finalize() en CLR, que es llamado por el GC durante la recolección de basura. Por lo tanto, el momento de la llamada no está claro. En C++/CLI en .NET 2.0, la semántica de los destructores se modificó para que sea equivalente al método Dispose(), lo que implica dos cosas:
1. Todas las clases CLR en C++/CLI implementan la interfaz IDisposable, por lo que puede usar la palabra clave using para acceder a instancias de esta clase en C#.
2. El destructor ya no es equivalente a Finalize().
Para el primer punto, esto es algo bueno, creo que semánticamente Dispose() está más cerca del destructor de C++. Con respecto al segundo punto, Microsoft ha ampliado introduciendo la función "!", como se muestra a continuación:
1 clase de referencia pública Foo
2 {
3 públicos:
4Foo();
5 ~Foo(); // destructor
6 !Foo(); // finalizador
7};
8
La función "!" (Realmente no sé cómo llamarla) reemplaza el Finalize() original en Managed C++ y es llamada por el GC. MSDN recomienda que para reducir la duplicación de código, pueda escribir código como este:
1 ~Foo()
2 {
3 //Liberar recursos administrados
4 esto->!Foo();
5}
6
7 !Foo()
8 {
9 //Liberar recursos no administrados
10}
11
Para la clase anterior, el código C# correspondiente generado por C++/CLI es en realidad el siguiente:
1 clase pública Foo
2 {
3 vacío privado! Foo()
4 {
5 // Liberar recursos no administrados
6}
7
8 vacío privado ~Foo()
9 {
10 // Liberar recursos administrados
11 !Foo();
12}
13
14 publicFoo()
15 {
16}
17
18 eliminación pública vacía ()
19 {
20 Eliminar (verdadero);
21 GC.SuppressFinalize(esto);
Veintidós }
veintitrés
24 eliminación de vacío virtual protegido (eliminación bool)
25 {
26 si (deshacerse)
27 {
28 ~Foo();
29 }
30 más
31 {
32 intento
33 {
34 !Foo();
35}
36 finalmente
37 {
38 base.Finalizar();
39 }
40}
41 }
42
43 vacío protegido Finalizar()
44 {
45 Disponer(falso);
46 }
47 }
48
Dado que ~Foo() y !Foo() no se llamarán repetidamente (al menos MS así lo cree), no hay variables en este código que sean las mismas que las del m_disposed anterior, pero la estructura básica es la misma.
Además, puede ver que en realidad no son ~Foo() y !Foo() las que son Dispose y Finalize, sino que el compilador C++/CLI genera dos funciones Dispose y Finalize y las llama en el momento apropiado. C++/CLI en realidad ha hecho mucho trabajo, pero el único problema es que depende de que el usuario llame a !Foo() en ~Foo().
En cuanto a la liberación de recursos, lo último que hay que mencionar es la función Cerrar. Es semánticamente muy similar a Dispose. Según MSDN, esta función se proporciona para que los usuarios se sientan más cómodos, porque los usuarios están más acostumbrados a llamar a Close() para ciertos objetos, como archivos.
Sin embargo, después de todo, estas dos funciones hacen lo mismo, por lo que el código recomendado por MSDN es:
1 vacío público Cerrar()
2 {
3 Disponer(();
4}
5
6
Aquí, la función Dispose sin parámetros se llama directamente para obtener la misma semántica que Dispose. Esto parece perfecto, pero por otro lado, si se proporcionan Dispose y Close al mismo tiempo, traerá cierta confusión a los usuarios. Sin ver los detalles del código, es difícil saber la diferencia entre estas dos funciones. Por lo tanto, las especificaciones de diseño del código .NET dicen que los usuarios en realidad sólo pueden utilizar una de estas dos funciones. Entonces el patrón sugerido es:
1 clase pública Foo: IDisposable
2 {
3 cierre público vacío ()
4 {
5 Desechar();
6}
7
8 anular IDisposable.Dispose()
9 {
10 Eliminar (verdadero);
11 GC.SuppressFinalize(esto);
12}
13
14 eliminación de vacío virtual protegido (eliminación bool)
15 {
16 // Igual que antes
17}
18}
19
Aquí se utiliza la llamada implementación explícita de la interfaz: void IDisposable.Dispose(). Solo se puede acceder a esta implementación explícita a través de la interfaz, pero no a través de la clase de implementación. por lo tanto:
1 Foo foo = nuevo Foo();
2
3 foo.Dispose(); // Error
4 (foo como IDisposable).Dispose(); // Correcto
5
Esto se ocupa de ambos. Para aquellos a quienes les gusta usar Close, pueden usar foo.Close() directamente y no verán Dispose(). Para aquellos a quienes les gusta Dispose, pueden convertir el tipo a IDisposable para llamar o usar la declaración de uso. ¡Gran alegría para ambos!
http://www.cnblogs.com/xlshcn/archive/2007/01/16/idisposable.html