Comment savoir si une référence d'objet IDisposable est supprimée?

85

Existe-t-il une méthode, ou un autre moyen léger, pour vérifier si une référence est à un objet supprimé?

PS - C'est juste une curiosité (dormez bien, pas dans le code de production). Oui, je sais que je peux attraper le ObjectDisposedExceptionen essayant d'accéder à un membre de l'objet.

Neil C. Obremski
la source
11
Je sais pas. Il semble curieux qu'il n'y ait pas de bool IsDisposed { get; }déclaration sur System.IDisposable.
nicodemus13
3
@ nicodemus13: La Disposeméthode demande à un objet de libérer toutes les ressources qu'il a acquises mais pas encore libérées. Si un objet ne contient jamais de ressources, sa Disposeméthode n'aura généralement rien à faire; si le type le déclare, void IDisposable.Dispose() {};il peut sinon ignorer IDisposablesans surcharge par instance. Une IsDisposedpropriété qui devait devenir vraie après un Disposeappel nécessiterait l'ajout d'un indicateur booléen autrement inutile à chaque instance de nombreux types qui pourraient autrement être ignorés Dispose.
supercat
1
Mais, partout où vous appelez une méthode sur un objet qui implémente IDisposable, comment pouvez-vous vérifier si elle a été éliminée en premier? Plutôt que de supposer que ce n'est pas le cas et d'attraper une exception? Ou d'une manière ou d'une autre, vous êtes censé gérer la vie de manière à toujours savoir si elle est éliminée ou non?
nicodemus13
3
@ nicodemus13: Il ne faut généralement pas utiliser un objet sans savoir qu'il n'a pas été et ne sera pas éliminé sauf dans les cas où l'on est prêt à considérer l'élimination de l'objet par un code extérieur comme un signal pour abandonner toute action en attente avec lui . Un IsDisposedindicateur peut aider à empêcher le code de perdre du temps sur des opérations qui ne peuvent pas réussir, mais il faudrait quand même gérer des exceptions dans le cas où un objet serait éliminé entre la IsDisposedvérification et la tentative de l'utiliser.
supercat
WeakReferencesemble pertinent ici. Ce n'est pas exactement un détecteur IDipose'd, mais il vous dit s'il est GC'd
Malachi

Réponses:

47

Non - l'implémentation par défaut du modèle IDisposable ne le prend pas en charge

Dandikas
la source
41

System.Windows.Forms.Controla une IsDisposedpropriété définie sur true après l' Dispose()appel . Dans vos propres objets IDisposable, vous pouvez facilement créer une propriété similaire.

Ryan Lundy
la source
L'OP cherchait à voir s'il existe déjà une propriété similaire sur des objets qu'il ne crée pas. Ce serait une bonne idée pour les objets que nous créons, mais la plupart des classes jetables dans .NET ne suivent pas cette convention. La réponse de Dandikas est correcte.
krillgar
2
@krillgar, il n'y a rien dans la question du PO qui soutient votre affirmation.
Ryan Lundy
18

Il n'y a rien de intégré qui permette cela. Vous devez exposer une propriété booléenne IsDisposed qui reflète un indicateur supprimé interne.

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
la source
BTW, si l'on commence à utiliser ce modèle, cela aide à définir une nouvelle interface ( IDisposablePlusou autre) qui hérite de IDisposableet inclut bool IsDisposed { get; }. Cela permet de savoir facilement lesquels de vos IDisposableobjets prennent en charge IsDisposed.
ToolmakerSteve
Je ne pense pas que vous puissiez hériter d'une interface à cause du fonctionnement de C #. Placer une interface après un deux-points en hérite. Je pense que cela implémenterait l'interface de l'autre.
Moïse
9

Si ce n'est pas votre classe et qu'elle ne fournit pas de propriété IsDisposed (ou quelque chose de similaire - le nom n'est qu'une convention), alors vous n'avez aucun moyen de le savoir.

Mais si c'est votre classe et que vous suivez l' implémentation canonique d'IDisposable , alors exposez simplement le champ _disposed ou _isDisposed en tant que propriété et vérifiez cela.

sauter
la source
2

La Disposeméthode est requise pour effectuer tout nettoyage requis avant qu'un objet ne soit abandonné; si aucun nettoyage n'est requis, il n'est pas nécessaire de faire quoi que ce soit. Exiger qu'un objet garde une trace de sa suppression, même lorsque la Disposeméthode ne ferait autrement rien, exigerait que de nombreux IDisposableobjets ajoutent un indicateur pour un bénéfice très limité.

Il aurait peut-être été utile d' IDisposableinclure deux propriétés - l'une indiquant si un objet devait être éliminé et l'autre indiquant que l'objet n'avait pas été rendu inutile par l'élimination. Pour les objets où l'élimination fait réellement quelque chose, les deux valeurs seraient initialement vraies et deviendraient fausses par la suite Dispose. Pour les objets où l'élimination n'a pas besoin de faire de nettoyage, la première méthode peut toujours retourner false et la seconde toujours true, sans avoir à stocker un indicateur n'importe où. Cependant, je ne pense pas qu'il soit possible de les ajouter à .NET maintenant.

supercat
la source
IMHO, deux drapeaux est exagéré. Je pense qu'il vaut mieux s'en tenir au paradigme habituel, où l'on a un seul drapeau une fois que Dispose a été appelé sur un objet. Sinon, vous ajoutez de la complexité, simplement pour savoir que certains objets "sont toujours utiles" même si Dispose a été appelé sur eux. Cela ne vaut pas la peine de suivre cette route.
ToolmakerSteve
@ToolmakerSteve: Il y aurait généralement zéro ou un indicateur. Pour les objets qui nécessitent une élimination, les propriétés «doit être éliminé» et «est utile» donneraient «vrai / vrai» avant de se débarrasser et «faux / faux» après, mais pour les objets où l'élimination serait un non-op, les deux seraient inconditionnellement retourne "faux / vrai". Dire qu'un objet doit encore être éliminé quand il ne le fait jamais, ou qu'un objet n'est pas utile quand il l'est toujours, serait plutôt dégoûtant. Je suppose qu'une autre approche consisterait à utiliser un type énuméré pour indiquer si un type doit être éliminé, a été supprimé ou tout simplement ne s'en soucie pas.
supercat
@ToolmakerSteve: Je pense que la principale raison pour laquelle il IDisposablen'y a pas de Disposedpropriété est qu'il aurait été perçu comme étrange d'avoir des objets sur lesquels l'appel Disposene définirait pas une telle propriété true, mais exigeant que les objets gardent une trace de l' Disposeappel dans les cas où autrement, ils n'auraient aucune raison de s'en soucier, ce qui ajouterait des coûts importants et peu d'avantages.
supercat
1

Je vois que c'est vieux, mais je n'ai pas vu de réponse. Certains objets jetables comme un DataSet n'ont pas tous un événement supprimé que vous pouvez attacher.

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;
    }
}
Moïse
la source
Bon à savoir. Plus précisément, l' Disposedévénement est un membre de l' System.ComponentModel.IComponentinterface.
ToolmakerSteve
-1

Ce que j'aime faire, c'est déclarer les objets sans les initialiser, mais définir leurs valeurs par défaut sur Nothing. Puis, à la fin de la boucle, j'écris:

If anObject IsNot Nothing Then anObject.Dispose()

Voici un échantillon complet:

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

Cela fonctionne également très bien pour mettre vos objets principaux au sommet d'une routine, les utiliser dans une Tryroutine, puis les disposer dans un Finallybloc:

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
la source
6
@ LarsHöppner: L'essence de la question est indépendante du langage, et les bons développeurs C # devraient probablement connaître au moins suffisamment de VB.NET pour lire le code ci-dessus (et les développeurs VB.NET devraient également apprendre suffisamment de C # pour lire du code C # qui ne le fait pas faire quelque chose de particulièrement exotique).
supercat
3
Pourquoi feriez-vous tout cela au lieu d'utiliser une Usinginstruction? Cela existait certainement en 2013, lorsque cette réponse a été rédigée.
Cody Gray
Vraiment "GoodExit:" c'est quoi ce 1983 pour un GOTO ?? Veuillez arrêter de l'utiliser.
Moses le
Cela ne répond pas à la question. Plus précisément, une fois inputPdfdéfini sur une valeur (autre que Nothing), votre réponse ne montre aucun moyen de savoir si elle inputPdfa été supprimée. Vous pouvez en partie résoudre ce problème en définissant inputPdf = Nothingaprès l'élimination. Cependant, cela n'aiderait pas les autres variables qui ont été pointées vers le même objet que inputPdf. C'est si vous le faites: inputPdf = New PdfReader, Dim pdf2 As PdfReader = inputPdf, inputPdf.Dispose, inputPdf = Nothing, il y aurait encore aucun moyen de savoir qui pdf2est disposé (il est le même objet que inputPdf).
ToolmakerSteve