¿Cómo se sabe si se elimina una referencia de objeto IDisposable?

86

¿Existe un método, o alguna otra forma liviana, para verificar si una referencia es a un objeto desechado?

PD - Esto es solo una curiosidad (duerma bien, no en código de producción). Sí, sé que puedo detectarlo ObjectDisposedExceptional intentar acceder a un miembro del objeto.

Neil C. Obremski
fuente
11
No sé. Parece curioso que no haya una bool IsDisposed { get; }declaración sobre System.IDisposable.
nicodemus
3
@ nicodemus13: el Disposemétodo ordena a un objeto que libere todos y cada uno de los recursos que ha adquirido pero que aún no ha liberado. Si un objeto nunca contiene recursos, su Disposemétodo generalmente no tendrá que hacer nada; si el tipo lo declara void IDisposable.Dispose() {};, puede ignorarlo IDisposablesin sobrecarga por instancia. Una IsDisposedpropiedad que se esperaba que se hiciera verdadera después de cualquier Disposellamada necesitaría agregar un indicador booleano que de otro modo sería innecesario a cada instancia de muchos tipos que de otro modo se podrían ignorar Dispose.
supercat
1
Pero, dondequiera que llame a un método en un objeto que se implementa IDisposable, ¿cómo puede verificar si se ha eliminado primero? ¿En lugar de asumir que no lo es y detectar una excepción? ¿O de alguna manera está destinado a administrar la vida para que siempre sepa si se desecha o no?
nicodemus 13 de
3
@ nicodemus13: En general, no se debe usar un objeto sin saber que no se ha eliminado ni se eliminará, excepto en los casos en que uno esté preparado para considerar la eliminación del objeto por código externo como una señal para abortar cualquier acción pendiente con él. . Una IsDisposedbandera puede ayudar a evitar que el código pierda tiempo en operaciones que posiblemente no puedan tener éxito, pero aún así sería necesario manejar excepciones en el caso de que un objeto se elimine entre la IsDisposedverificación y el intento de usarlo.
supercat
WeakReferenceparece relevante aquí. No es exactamente un detector IDipose'd, pero te dice si es GC'd
Malaquías

Respuestas:

48

No, la implementación predeterminada del patrón IDisposable no lo admite

Dandikas
fuente
41

System.Windows.Forms.Controltiene una IsDisposedpropiedad que se establece en true después de que Dispose()se llame . En sus propios objetos IDisposable, puede crear fácilmente una propiedad similar.

Ryan Lundy
fuente
El OP estaba buscando ver si ya hay una propiedad similar en los objetos que no está creando. Esta sería una buena idea para los objetos que creamos, pero la mayoría de las clases desechables en .NET no siguen esta convención. La respuesta de Dandikas es correcta.
krillgar
2
@krillgar, no hay nada en la pregunta del OP que respalde su afirmación.
Ryan Lundy
18

No hay nada integrado que lo permita. Debería exponer una propiedad booleana IsDisposed que refleje una marca de disposición interna.

public class SimpleCleanup : IDisposable
{
    private bool disposed = false;

    public bool IsDisposed
    {
       get
       {
          return disposed;
       }
    }

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
               // free only managed resources here
            }

            // free unmanaged resources here
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
    }
}
Scott Dorman
fuente
Por cierto, si uno comienza a usar este patrón, ayuda a definir una nueva interfaz ( IDisposablePluso lo que sea) que hereda IDisposablee incluye bool IsDisposed { get; }. Esto facilita saber cuál de sus IDisposableobjetos admite IsDisposed.
ToolmakerSteve
No creo que pueda heredar una interfaz debido a cómo funciona C #. Colocar una interfaz después de dos puntos la hereda. Espero que implemente la interfaz en el otro.
Moisés
9

Si no es su clase y no proporciona una propiedad IsDisposed (o algo similar, el nombre es solo una convención), entonces no tiene forma de saberlo.

Pero si es su clase y está siguiendo la implementación canónica IDisposable , simplemente exponga el campo _disposed o _isDisposed como una propiedad y verifique eso.

jop
fuente
2

El Disposemétodo es necesario para realizar cualquier limpieza necesaria antes de que se abandone un objeto; si no se requiere limpieza, no se requiere hacer nada. Exigir que un objeto realice un seguimiento de si se ha eliminado, incluso cuando el Disposemétodo no haría nada de otra manera, requeriría que muchos IDisposableobjetos agreguen una bandera para un beneficio muy limitado.

Podría haber sido útil IDisposableincluir dos propiedades: una que indicaba si un objeto necesitaba desecharse y otra que indicaba que el objeto no se había vuelto inútil por desecho. Para los objetos en los que la eliminación realmente hace algo, ambos valores serían inicialmente verdaderos y luego se volverían falsos Dispose. Para los objetos en los que la eliminación no necesita realizar ninguna limpieza, el primer método siempre podría devolver falso y el segundo siempre verdadero, sin tener que almacenar una bandera en ningún lugar. Sin embargo, no creo que haya ninguna forma de agregarlos a .NET ahora.

Super gato
fuente
En mi humilde opinión, dos banderas es una exageración. Creo que es mejor seguir con el paradigma habitual, donde uno tiene una sola bandera una vez que se ha llamado a Dispose en un objeto. De lo contrario, agrega complejidad, simplemente para saber que ciertos objetos "siguen siendo útiles" a pesar de que se ha llamado a Dispose. No vale la pena ir por ese camino.
ToolmakerSteve
@ToolmakerSteve: Por lo general, habría cero o una bandera. Para los objetos que requieren eliminación, las propiedades "necesita eliminación" y "es útil" producirían "verdadero / verdadero" antes de la eliminación y "falso / falso" después, pero para los objetos en los que la eliminación sería una operación no operativa, ambas incondicionalmente devuelve "falso / verdadero". Decir que un objeto todavía necesita desecharse cuando nunca lo hace, o que un objeto no es útil cuando siempre lo es, sería bastante desagradable. Supongo que otro enfoque sería usar un tipo enumerado para indicar si un tipo necesita eliminarse, se ha eliminado o simplemente no le importa.
supercat
@ToolmakerSteve: Creo que la gran razón por la IDisposableque no tiene una Disposedpropiedad es que se habría percibido como extraño tener objetos en los que llamar Disposeno establecería dicha propiedad true, pero requiriendo que los objetos realicen un seguimiento de si Disposese llamó en los casos en que de lo contrario, no tendrían razón para preocuparse, lo que sumaría un costo significativo y un beneficio mínimo.
supercat
1

Veo que esto es antiguo, pero no vi una respuesta. Algunos no todos los objetos desechables como un DataSet tienen un evento desechado que puede adjuntar.

class DisposeSample : IDisposable
{
    DataSet myDataSet = new DataSet();
    private bool _isDisposed;

    public DisposeSample()
    {
        // attach dispose event for myDataSet
        myDataSet.Disposed += MyDataSet_Disposed;
    }

    private void MyDataSet_Disposed(object sender, EventArgs e)
    {
        //Event triggers when myDataSet is disposed
        _isDisposed = true; // set private bool variable as true 
    }


    public void Dispose()
    {
        if (!_isDisposed) // only dispose if has not been disposed;
            myDataSet?.Dispose(); // only dispose if myDataSet is not null;
    }
}
Moisés
fuente
Bueno saber. Específicamente, el Disposedevento es un miembro de la System.ComponentModel.IComponentinterfaz.
ToolmakerSteve
-1

Lo que me gusta hacer es declarar los objetos sin inicializarlos, pero establecer sus valores predeterminados en Nothing. Luego, al final del ciclo escribo:

If anObject IsNot Nothing Then anObject.Dispose()

Aquí tienes una muestra completa:

Public Sub Example()
    Dim inputPdf As PdfReader = Nothing, inputDoc As Document = Nothing, outputWriter As PdfWriter = Nothing

    'code goes here that may or may not end up using all three objects, 
    ' such as when I see that there aren't enough pages in the pdf once I open  
    ' the pdfreader and then abort by jumping to my cleanup routine using a goto ..

GoodExit:
    If inputPdf IsNot Nothing Then inputPdf.Dispose()
    If inputDoc IsNot Nothing Then inputDoc.Dispose()
    If outputWriter IsNot Nothing Then outputWriter.Dispose()
End Sub

Esto también funciona muy bien para colocar sus objetos principales en la parte superior de una rutina, usarlos dentro de una Tryrutina y luego desecharlos en un Finallybloque:

Private Sub Test()
    Dim aForm As System.Windows.Forms.Form = Nothing
    Try
        Dim sName As String = aForm.Name  'null ref should occur
    Catch ex As Exception
        'got null exception, no doubt
    Finally
        'proper disposal occurs, error or no error, initialized or not..
        If aForm IsNot Nothing Then aForm.Dispose()
    End Try
End Sub
JeffreyDurham
fuente
6
@ LarsHöppner: La esencia de la pregunta es independiente del lenguaje, y los buenos desarrolladores de C # probablemente deberían saber al menos suficiente VB.NET para leer el código anterior (y los desarrolladores de VB.NET también deberían aprender suficiente C # para leer el código C # que no hacer algo particularmente exótico).
supercat
3
¿Por qué harías todo esto en lugar de usar una Usingdeclaración? Eso ciertamente existía en 2013 cuando se escribió esta respuesta.
Cody Gray
Realmente "GoodSalir:" ¿Qué es este 1983 para un GOTO? Deja de usar eso.
Moisés
Esto no responde a la pregunta. Específicamente, una vez que inputPdfse ha establecido en un valor (que no sea Nada), su respuesta no muestra forma de saber si inputPdfse ha eliminado. En parte, podría abordar esto colocando inputPdf = Nothingdespués de desechar. Sin embargo, esto no ayudaría a ninguna otra variable que se haya apuntado al mismo objeto que inputPdf. Esto es, si lo hace: inputPdf = New PdfReader, Dim pdf2 As PdfReader = inputPdf, inputPdf.Dispose, inputPdf = Nothing, todavía habría ninguna manera de saber que pdf2está dispuesto (que es el mismo objeto que inputPdf).
ToolmakerSteve