web-dev-qa-db-fra.com

Jeter, quand est-il appelé?

Considérez le code suivant:

namespace DisposeTest
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Calling Test");

            Test();

            Console.WriteLine("Call to Test done");
        }

        static void Test()
        {
            DisposeImplementation di = new DisposeImplementation();
        }
    }

    internal class DisposeImplementation : IDisposable
    {
        ~DisposeImplementation()
        {
            Console.WriteLine("~ in DisposeImplementation instance called");
        }
        public void Dispose()
        {
            Console.WriteLine("Dispose in DisposeImplementation instance called");
        }
    }
}

Le Dispose n'est jamais appelé, même si je mets une boucle d'attente après l'invocation de Test();. Donc ça craint. Je veux écrire une classe qui est simple et très facile à utiliser, pour m'assurer que toutes les ressources possibles sont nettoyées. Je ne veux pas donner cette responsabilité à l'utilisateur de ma classe.

Solution possible: utilisez using, ou appelez Dispose moi-même (fondamentalement le même). Puis-je forcer l'utilisateur à utiliser une utilisation? Ou puis-je forcer l'appel à disposer?

L'appel de GC.Collect(); après Test(); ne fonctionne pas non plus.

Mettre di dans null n'invoque pas Dispose non plus. Le déconstructeur fonctionne, donc l'objet est déconstruit lorsqu'il quitte Test()

Ok les gars, c'est clair maintenant!

Merci à tous pour vos réponses! J'ajouterai un avertissement dans le commentaire!

32
Snake

Je veux écrire une classe qui est simple et très facile à utiliser, pour m'assurer que toutes les ressources possibles sont nettoyées. Je ne veux pas donner cette responsabilité à l'utilisateur de ma classe.

Tu ne peux pas faire ça. La gestion de la mémoire n'est tout simplement pas conçue pour accueillir des ressources qui ne sont pas spécifiquement de la mémoire.

Le modèle IDisposable est destiné aux développeurs comme un moyen de dire à un objet quand ils en ont fini, au lieu d'avoir la gestion de la mémoire essayant de comprendre cela en utilisant des choses comme le comptage de références.

Vous pouvez utiliser le Finalizer comme solution de rechange pour les utilisateurs qui ne parviennent pas à éliminer correctement les objets, mais il ne fonctionne pas bien comme méthode principale de nettoyage des objets. Pour fonctionner correctement, les objets doivent être correctement éliminés, de sorte que le Finalizer plus coûteux n'a jamais besoin d'être appelé.

20
Guffa

Il convient de souligner deux points importants pour répondre à la question du PO:

  1. .NET GC n'est pas déterministe (c'est-à-dire que vous ne savez jamais et ne devez pas dépendre quand cela se produit)
  2. Dispose n'est jamais appelé par le .NET Framework; vous devez l'appeler manuellement - de préférence en encapsulant sa création dans un bloc using().
  3. Définir explicitement un objet jetable sur null sans appeler Dispose () dessus est une mauvaise chose à faire. Ce qui se passe, c'est que vous définissez explicitement la "référence racine" des objets sur null. Cela signifie en fait que vous ne pouvez pas appeler Dispose plus tard ET plus important encore, il envoie l'objet à la file d'attente de finalisation du GC pour la finalisation. Causer la finalisation par de mauvaises pratiques de programmation doit être évité à tout prix.

Finalizer: Certains développeurs l'appellent un destructeur. Et en fait, il est même appelé un destructeur dans les versions C # 4.0 Language Spec (section 1.6.7.6) et précédent du courant spécification ECMA-334. Heureusement, la 4e édition (juin 2006) définit correctement les finaliseurs dans la section 8.7.9 et tente de dissiper la confusion entre les deux dans la section 17.12. Il convient de noter qu'il existe d'importantes différences internes (pas besoin d'entrer dans ces détails sanglants ici) entre ce qui est traditionnellement appelé destructeur et destructeur/finaliseur dans le .NET Framework.

  1. Si un Finalizer est présent, il sera appelé par le .NET Framework si et seulement si GC.SuppressFinalize() n'est pas appelé.
  2. Vous ne devez JAMAIS appeler explicitement un finaliseur. Heureusement, C # ne le permettra pas explicitement (je ne connais pas les autres langages); bien qu'il puisse être forcé en appelant GC.Collect(2) pour la 2ème génération du GC.

Finalisation: La finalisation est le moyen du .NET Framework pour gérer le nettoyage et la libération "gracieux" des ressources.

  1. Il se produit uniquement lorsqu'il existe des objets dans la file d'attente de finalisation.
  2. Cela se produit uniquement lorsqu'une récupération de place se produit pour Gen2 (qui est environ 1 sur 100 collections pour une application .NET bien écrite).
  3. Jusqu'au .NET 4 inclus, il existe un seul thread de finalisation. Si ce fil se bloque pour une raison quelconque, votre application est vissée.
  4. Écrire du code de finalisation correct et sûr n'est pas anodin et des erreurs peuvent être commises assez facilement (c'est-à-dire permettre accidentellement de lever des exceptions depuis le Finalizer, autoriser les dépendances sur d'autres objets qui pourraient déjà être finalisés, etc.)

Bien que ce soit certainement plus d'informations que vous avez demandées, cela fournit des informations sur la façon dont les choses fonctionnent et pourquoi elles fonctionnent comme elles le font. Certaines personnes diront qu'elles ne devraient pas avoir à se soucier de la gestion de la mémoire et des ressources dans .NET, mais cela ne change pas le fait que cela doit être fait - et je ne vois pas cela disparaître dans un avenir proche.

46
Dave Black

Toutes les réponses sont (plus ou moins) correctes, voici un exemple:

static void Test()
{
    using (DisposeImplementation di = new DisposeImplementation())
    {
        // Do stuff with di
    }
}

L'appel manuel de Dispose fonctionnera également, mais l'avantage de l'instruction using est que l'objet sera également supprimé lorsque vous quitterez le bloc de contrôle car une exception est levée.

Vous pouvez ajouter un finaliseur qui gère l'élimination des ressources au cas où quelqu'un "oublie" d'utiliser l'interface IDisposable:

public class DisposeImplementation : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    ~DisposeImplementation()
    {
        Dispose(false);
    }
}

Voir cette question pour plus d'informations. Cependant, cela ne fait que compenser les personnes qui n'utilisent pas correctement votre classe :) Je vous suggère d'ajouter un gros appel de Debug.Fail() au Finalizer, pour avertir le développeur de leur erreur.

Si vous choisissez d'implémenter le modèle, vous verrez que GC.Collect() déclenchera l'élimination.

13
Thorarin

Utilisez-le comme modèle/modèle pour vos cours

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

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    private void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if (!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if (disposing)
            {
                // Dispose managed resources.                
                ......
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            ...........................

            // Note disposing has been done.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~MyClass()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }
}

Et bien sûr, comme mentionné par d'autres, n'oubliez pas le bloc using(...){}.

7
Incognito

Vous devrez appeler Dispose explicitement ou en encapsulant l'objet dans une instruction using. Exemple:

using (var di = new DisposeImplementation())
{
}

Solution possible: utilisez using ou appelez Dispose yourself (fondamentalement la même).

Utiliser using équivaut à appeler Dispose dans un bloc finally.

2
Anne Sharp

Vous êtes censé en disposer vous-même, en appelant la méthode Dispose ou en utilisant using. Rappelez-vous, ce n'est pas un déconstructeur!

Si vous ne pouvez pas faire confiance aux utilisateurs de votre classe pour disposer correctement des ressources, ils vont probablement gâcher d'autres façons de toute façon.

1
Hans Olsson

Dispose n'est pas appelé automatiquement. Vous devez utiliser une clause using pour encapsuler l'utilisation ou l'appeler manuellement.

Voir http://msdn.Microsoft.com/en-us/library/aa664736%28VS.71%29.aspx

Et juste pour anticiper une autre idée que vous pourriez avoir: vous ne pouvez pas appeler dispose depuis le destructeur ... J'ai essayé cela il y a quelque temps dans un projet.

1
Rodrick Chapman