Question

J'ai une classe C# avec un Dispose fonction via IDisposable.Il est destiné à être utilisé à l'intérieur d'un using bloquer afin que la ressource coûteuse qu’il gère puisse être libérée immédiatement.

Le problème est qu'un bug s'est produit lorsqu'une exception était levée avant Dispose a été appelé et le programmeur a négligé d'utiliser using ou finally.

En C++, je n'ai jamais eu à m'inquiéter de ça.L'appel au destructeur d'une classe serait automatiquement inséré à la fin de la portée de l'objet.La seule façon d'éviter que cela se produise serait d'utiliser l'opérateur new et de maintenir l'objet derrière un pointeur, mais cela nécessiterait un travail supplémentaire de la part du programmeur et ce n'est pas quelque chose qu'il ferait par accident, comme oublier d'utiliser using.

Y a-t-il un moyen pour un using bloc à utiliser automatiquement en C# ?

Merci beaucoup.

MISE À JOUR:

Je voudrais expliquer pourquoi je n'accepte pas les réponses du finaliseur.Ces réponses sont techniquement correctes en elles-mêmes, mais ce ne sont pas des destructeurs de style C++.

Voici le bug que j'ai trouvé, réduit à l'essentiel...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

En utilisant FXCop est une excellente suggestion, mais si c'est ma seule réponse, ma question devrait devenir un plaidoyer auprès des utilisateurs de C#, ou utiliser C++.Vingt instructions using imbriquées, ça vous tente ?

Était-ce utile?

La solution

Malheureusement, il n'existe aucun moyen de le faire directement dans le code.S'il s'agit d'un problème en interne, il existe différentes solutions d'analyse de code qui pourraient détecter ce type de problèmes.Avez-vous étudié FxCop ?Je pense que cela permettra de détecter ces situations et dans tous les cas où des objets IDisposable pourraient rester en suspens.S'il s'agit d'un composant que des personnes utilisent en dehors de votre organisation et que vous ne pouvez pas exiger FxCop, alors la documentation est vraiment votre seul recours :).

Modifier:Dans le cas des finaliseurs, cela ne garantit pas vraiment quand la finalisation aura lieu.Cela peut donc être une solution pour vous mais cela dépend de la situation.

Autres conseils

Là où je travaille, nous utilisons les directives suivantes :

  • Chaque classe IDisposable doit avoir un finaliseur
  • Chaque fois que vous utilisez un objet IDisposable, il doit être utilisé à l'intérieur d'un bloc "using".La seule exception est si l'objet est membre d'une autre classe, auquel cas la classe conteneur doit être IDisposable et doit appeler la méthode « Dispose » du membre dans sa propre implémentation de « Dispose ».Cela signifie que « Dispose » ne doit jamais être appelé par le développeur, sauf dans une autre méthode « Dispose », éliminant ainsi le bogue décrit dans la question.
  • Le code de chaque finaliseur doit commencer par un journal d'avertissements/erreurs nous informant que le finaliseur a été appelé.De cette façon, vous avez de très bonnes chances de repérer les bogues comme décrit ci-dessus avant de publier le code, et cela pourrait également être un indice de bogues survenant dans votre système.

Pour nous faciliter la vie, nous avons également une méthode SafeDispose dans notre infrastructure, qui appelle la méthode Dispose de son argument dans un bloc try-catch (avec journalisation des erreurs), juste au cas où (bien que les méthodes Dispose ne soient pas censées lever des exceptions). ).

Voir également: Chris LyonSuggestions de concernant IDisposable

Modifier:@Querelleur:Une chose que vous devez faire est d'appeler GC.SuppressFinalize dans 'Dispose', de sorte que si l'objet était supprimé, il ne serait pas "re-disposé".

Il est également généralement conseillé de brandir un drapeau indiquant si l'objet a déjà été éliminé ou non.Le modèle suivant est généralement assez bon :

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

Bien entendu, le verrouillage n'est pas toujours nécessaire, mais si vous n'êtes pas sûr si votre classe sera utilisée ou non dans un environnement multithread, il est conseillé de la conserver.

@Querelleur

If sera appelé lorsque l'objet est déplacé hors de portée et est rangé par le garbage collector.

Cette déclaration est trompeuse et la façon dont je la lis est incorrecte :Il n'y a absolument aucune garantie quant au moment où le finaliseur sera appelé.Vous avez tout à fait raison de dire que billpg devrait implémenter un finaliseur ;cependant, il ne sera pas appelé automatiquement lorsque l'objet sort de la portée comme il le souhaite. Preuve, le premier point sous Les opérations de finalisation présentent les limitations suivantes.

En fait, Microsoft a accordé une subvention à Chris Sells pour créer une implémentation de .NET utilisant le comptage de références au lieu du garbage collection. Lien.Il s'est avéré qu'il y avait un considérable les performances ont atteint.

~ClassName()
{
}

EDIT (gras) :

If sera appelé lorsque l'objet est déplacé hors de portée et est rangé par le ramasse-miettes cependant, cela n'est pas déterministe et il n'est pas garanti qu'il se produise à un moment donné..C'est ce qu'on appelle un finaliseur.Tous les objets avec un finaliseur sont placés dans une file d'attente de finalisation spéciale par le garbage collector où la méthode finalize est invoquée sur eux (c'est donc techniquement un impact sur les performances que de déclarer des finaliseurs vides).

Le modèle de suppression « accepté » selon les directives du cadre est le suivant avec les ressources non gérées :

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

Ainsi, ce qui précède signifie que si quelqu'un appelle Dispose, les ressources non gérées sont rangées.Cependant, dans le cas où quelqu'un oublie d'appeler Dispose ou si une exception empêche Dispose d'être appelé, les ressources non gérées seront toujours rangées, seulement un peu plus tard, lorsque le GC mettra ses mitaines sales dessus (ce qui inclut la fermeture ou la fin inattendue de l'application). ).

La meilleure pratique consiste à utiliser un finaliseur dans votre classe et à toujours utiliser using blocs.

Il n'y a pas vraiment d'équivalent direct, les finaliseurs ressemblent à des destructeurs C, mais se comportent différemment.

Tu es censé nicher using blocs, c'est pourquoi la disposition du code C# les place par défaut sur la même ligne...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

Quand tu n'utilises pas using de toute façon, vous pouvez simplement faire ce qu'il fait sous le capot :

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

Avec le code managé, C# est très très efficace pour gérer sa propre mémoire, même lorsque les éléments sont mal éliminés.Si vous avez souvent affaire à des ressources non gérées, ce n'est pas si efficace.

Ce n'est pas différent d'un programmeur oubliant d'utiliser supprimer en C++, sauf qu'au moins ici, le ramasse-miettes finira par le rattraper.

Et vous n'aurez jamais besoin d'utiliser IDisposable si la seule ressource qui vous inquiète est la mémoire.Le framework gérera cela tout seul.IDisposable est uniquement destiné aux ressources non gérées telles que les connexions à la base de données, les flux de fichiers, les sockets, etc.

Une meilleure conception consiste à faire en sorte que cette classe libère la ressource coûteuse d'elle-même, avant de la supprimer.

Par exemple, s'il s'agit d'une connexion à une base de données, connectez-vous uniquement lorsque cela est nécessaire et libérez-le immédiatement, bien avant que la classe réelle ne soit supprimée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top