Fermer et éliminer - qui appeler?

Réponses:

191

Je veux clarifier cette situation.

Selon les directives de Microsoft, il est recommandé de fournir une Closeméthode le cas échéant. Voici une citation des directives de conception du cadre

Envisagez de fournir une méthode Close(), en plus de la Dispose()terminologie standard, si close est dans le domaine. Ce faisant, il est important de rendre l' Closeimplémentation identique à Dispose...

Dans la plupart des cas Close, les Disposeméthodes sont équivalentes. La principale différence entre Closeet Disposedans le cas de SqlConnectionObjectest:

Une application peut appeler Closeplusieurs fois. Aucune exception n'est générée.

Si vous avez appelé la Disposeméthode SqlConnection, l'état de l'objet sera réinitialisé. Si vous essayez d'appeler une méthode sur un SqlConnection objet supprimé , vous recevrez une exception.

Cela dit:

  • Si vous utilisez un objet de connexion une fois, utilisez Dispose.
  • Si l'objet de connexion doit être réutilisé, utilisez la Closeméthode.
aku
la source
5
@Chris, la documentation pour Close () dit "Il libère ensuite la connexion au pool de connexions, ou ferme la connexion si le pool de connexions est désactivé." So Close () devrait être suffisant pour empêcher le pool de connexions de déborder.
David Hammond
@DavidHammond: Vous avez raison. Je supprime mon commentaire précédent.
NotMe
3
Est-ce que .Dispose () libère également la connexion dans le pool?
oscilatingcretin
C'est le meilleur argument que j'ai lu sur le sujet d'une manière ou d'une autre en une décennie. Excellent point.
Michael Erickson
1
Donc ça marche comme ça 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
shaijut
24

Comme d'habitude, la réponse est: cela dépend. Différentes classes sont implémentées IDisposablede différentes manières, et c'est à vous de faire les recherches nécessaires.

Pour autant SqlClient, la pratique recommandée est de faire ce qui suit:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Vous devriez appeler Dispose(ou Close*) sur la connexion! N'attendez pas que le ramasse-miettes nettoie votre connexion, cela bloquera les connexions dans le pool jusqu'au prochain cycle GC (au moins). Si vous appelez Dispose, il n'est pas nécessaire d'appeler Close, et comme la usingconstruction le rend si facile à gérer Disposecorrectement, il n'y a vraiment aucune raison d'appeler Close.

Les connexions sont automatiquement regroupées et l'appel Dispose/ Closesur la connexion ne ferme pas physiquement la connexion (dans des circonstances normales). N'essayez pas de mettre en œuvre votre propre regroupement. SqlClienteffectue le nettoyage de la connexion lorsqu'elle est extraite du pool (comme la restauration du contexte de la base de données et des options de connexion).

* si vous appelez Close, assurez-vous de le faire d'une manière sûre pour les exceptions (c'est-à-dire dans un bloc catch ou enfin).

Brannon
la source
Quand vous dites: «c'est à vous de faire les recherches nécessaires», quelle est cette recherche? La seule façon dont je sais comment dire avec certitude est la réflexion, mais cela a l'inconvénient d'être «illégal» dans la plupart des situations.
Tempête du
7
Je ne dirais pas: conn.Close(); // Optionalce n'est pas facultatif. C'est redondant et inutile. Vous supprimez l'objet deux fois et cela sera marqué comme un avertissement par certains outils d'analyse de code.
Metalogic
@Metalogic Je suis d'accord qu'il est redondant et inutile (et moche) d'appeler Close avec des utilisations appropriées. Cependant, pinailler: appeler Close n'est pas "éliminer" (alors que Dispose implique Close pour un SqlConnection). Comparer à using (var x = ..) { x.Dispose(); }, auquel cas xest vraiment "disposé deux fois".
user2864740
11

Vous devez appeler Dispose ()!

Dispose () est pour le développeur à appeler, le Garbage Collector appelle Finalize (). Si vous n'appelez pas Dispose () sur vos objets, les ressources non gérées qu'ils ont utilisées ne seront pas supprimées tant que le garbage collector ne viendra pas et n'appellera pas finalize sur eux (et qui sait quand cela se produira).

Ce scénario est appelé Finalisation non déterministe et est un piège courant pour les développeurs .net. Si vous travaillez avec des objets qui implémentent IDisposable, appelez Dispose () sur eux!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Bien qu'il puisse y avoir de nombreuses instances (comme sur SqlConnection) où vous appelez Disponse () sur un objet et qu'il appelle simplement Close () sur sa connexion ou ferme un descripteur de fichier, il est presque toujours préférable d'appeler Dispose ()! sauf si vous prévoyez de réutiliser l'objet dans un avenir très proche.

Tyler
la source
26
Ce commentaire est totalement faux. Le garbage collector n'appelle jamais, jamais Dispose.
Stephen Cleary
3
Corollaire: Vous devriez appeler Dispose() si vous n'utilisez pas using()avec une classe qui implémente IDisposable. Si la classe appelée implémente IDisposable et que vous avez enveloppé son utilisation sur la page à l'intérieur using(), vous pouvez vous en débarrasser avec le Dispose()(jeu de mots prévu, alors tirez-moi). L'utilisation Close(), cependant, est recommandée avec tout ce qui utilise explicitement Open()AFAIK.
René Kåbis
Je ne suis pas sûr des autres SGBD, mais vous ne pouvez PAS faire les deux dans PostgreSql . Une fois Closela connexion établie, Postgres définit automatiquement l'identifiant de connexion sur null. A partir de là, on ne peut plus Disposeun identifiant de connexion sql qui est déjà défini sur null.
ssd
10

Car SqlConnection, du point de vue de la connexion elle-même, ils sont équivalents. Selon Reflector, les Dispose()appels Close()ainsi que quelques opérations supplémentaires de libération de mémoire, principalement en définissant des membres égaux à null.

Pour Stream, ils sont en fait équivalents. Stream.Dispose()appelle simplement Close ().

Curt Hagenlocher
la source
1
Êtes-vous sûr? MSDN dit qu'il est hérité deComponent ce qui ne semble rien faire pour essayer d'appelerClose() . Je ne vois nulle part DBConnectionou SqlConnectionqui est lié à l'une de ces notifications. Il a cependant un privé DisposeMe()qui n'est référencé nulle part .
Deanna
@Deanna, il est remplacé ici: github.com/dotnet/corefx/blob/…
David Cumps
@DavidCumps Il semble que cela ait changé en 4 ans depuis que j'ai écrit ce commentaire. Mes liens ne sont plus valides.
Deanna
6

Ce conseil rapide potentiel est devenu une longue réponse. Désolé.

Comme l'a souligné Tyler dans sa belle réponse, l'appel Dispose()est une excellente pratique de programmation. C'est parce que cette méthode est censée «rassembler» toutes les ressources nécessaires pour libérer des ressources afin qu'il n'y ait pas de ressources ouvertes inutiles. Si vous avez écrit du texte dans un fichier, par exemple, et que vous n'avez pas réussi à fermer le fichier (libérer la ressource), il restera ouvert et personne d'autre ne pourra y écrire jusqu'à ce que le GC arrive et fasse ce que vous devriez avoir terminé.

Maintenant, dans certains cas, il y aura des méthodes de "finalisation" plus spécifiques à la classe avec laquelle vous avez affaire, comme StreamWriter.Close(), qui remplace TextWriter.Close(). En effet, ils sont généralement plus adaptés à la situation: un StreamWriter Close(), par exemple, vide le flux et l'encodeur sous-jacent avant la création Dispose()de l'objet! Cool!

Cependant, en parcourant MSDN, vous constaterez que même Microsoft est parfois confus par la multitude de fermetures et de disposers. Dans cette page Web , par exemple, dans certains exemples, il Close()est appelé avant l'implicite Dispose()(voir l' instruction using si vous ne comprenez pas pourquoi c'est implicite), et dans un en particulier, ils ne se soucient pas. Pourquoi cela serait-il? Moi aussi, j'étais perplexe.

La raison pour laquelle j'ai pensé (et, je le souligne, il s'agit d' une recherche originale et je pourrais sûrement perdre ma réputation si je me trompe) est que cela Close()pourrait échouer, ce qui Dispose()donnerait une exception tout en laissant les ressources ouvertes, tout en les libérant sûrement . C'est pourquoi un Dispose()devrait toujours sauvegarder un Close()appel (désolé pour le jeu de mots).

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

Et oui, je suppose que Microsoft a glissé sur cet exemple. Peut-être que cet horodatage ne serait jamais vidé dans le fichier.

Je corrige mon ancien code demain.

Edit: désolé Brannon, je ne peux pas commenter votre réponse, mais êtes-vous sûr que c'est une bonne idée d'appeler Close()sur un finallybloc? Je suppose qu'une exception à cela pourrait ruiner le reste du bloc, qui contiendrait probablement un code de nettoyage important.

Réponse à Brannon: super, n'oubliez pas d'appeler Close()quand c'est vraiment nécessaire (par exemple lorsque vous traitez avec des flux - je ne sais pas grand-chose sur les connexions SQL dans .NET).

André Chalella
la source
En fait, je n'appelle jamais Close (), je laisse juste Dispose () et la construction 'using' faire ce qu'il faut . Si vous n'appelez pas Dispose, vous devez appeler Close d'une manière sécurisée contre les exceptions. Il peut être judicieux d'ajouter la gestion des exceptions au bloc finally.
Brannon le
À droite, mes commentaires concernaient spécifiquement SqlClient. Le fait est que vous devez comprendre les classes que vous utilisez. Appeler toujours Dispose n'est pas nécessairement la bonne réponse.
Brannon le
2

Tapez sur iDisposable et appelez disposer à ce sujet. Cela invoquera la méthode configurée pour implémenter "iDisposable.Dispose", quel que soit le nom de la fonction.

supercat
la source
La "fonction s'appelle" "Dispose": nous revenons donc à la question initiale:}
user2864740
La fonction est liée à IDisposable.Dispose, mais cela ne signifie pas que c'est le nom. Notez que dans vb.net, il est possible qu'une fonction soit liée à plusieurs membres d'interface avec des noms qui n'ont pas besoin d'être liés à celui de la fonction.
supercat
Cast comme ceci:using (myObj as IDisposable)
Yousha Aleayoub
2

En général, nous sommes confrontés à un problème dans Close (), Abort () et Dispose (), mais laissez-moi vous dire la différence entre eux.

1) ABORT: - Je ne suggérerai pas de l'utiliser car lorsque l'abandon est appelé, le client supprimera la connexion sans en informer le serveur afin que le serveur attende pendant un certain temps (environ 1 min). Si vous avez une demande en masse, vous ne pouvez pas utiliser abort () car cela peut entraîner l'expiration de votre pool de connexions limité.

2) Fermer: - Fermer est un très bon moyen de fermer la connexion car lors de la fermeture de la connexion, il appellera le serveur et accusera le serveur de se fermer par ce côté également.

Ici, encore une chose à regarder. Dans certains cas, si une erreur se produit, ce n'est pas un bon moyen d'écrire enfin du code dans cette connection.close () car à ce moment-là, l'état de communication sera défaillant.

3) Éliminer: - C'est un type de fermeture, mais après avoir fermé la connexion, vous ne pouvez plus l'ouvrir.

Alors essayez de cette façon,

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }
Jadia profond
la source
La vérification client != nullest incorrecte / trompeuse car elle ne protège pas toutes les utilisations. De plus, je ne suis pas sûr de savoir comment le code peut atteindre l'état "cette connexion n'est pas ouverte et devrait être fermée".
user2864740