Qu'advient-il d'un objet IDisposable créé au milieu d'une instruction pour laquelle je ne parviens pas explicitement à appeler Dispose ()?

StackOverflow https://stackoverflow.com/questions/415313

Question

Disons que je travaille avec Sharepoint (cela s'applique également à d'autres modèles d'objet) et qu'au milieu de ma déclaration, j'appelle une méthode, dans ce cas "OpenWeb ()", qui crée un objet SPisable IDisposable. . Maintenant, je ne peux pas appeler Dispose () sur l'objet SPWeb car je n'en ai pas la référence. Dois-je m'inquiéter de cette fuite de mémoire?

SPUser spUser = SPControl.GetContextSite(HttpContext.Current).OpenWeb().SiteUsers[@"foo\bar"];

Je sais que j'aurais pu diviser la déclaration en plusieurs lignes et obtenir la référence SPWeb pour appeler Dispose:

SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb();
SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
spWeb.Dispose();

N'oubliez pas que ma question ne concerne pas l'esthétique, mais davantage sur ce qu'il advient de l'objet IDisposable sur lequel je ne peux pas appeler explicitement Dispose (), car je n'ai pas la référence.

Désolé de ne pas être assez clair lorsque j'ai posé la question pour la première fois. Je l'ai depuis reformulé. Merci pour toutes les réponses reçues.

Était-ce utile?

La solution

"Que se passe-t-il avec l'objet IDisposable sur lequel je ne peux pas appeler explicitement Dispose ()?"

En général, vous pouvez appeler Dispose (implicitement avec une instruction using ou explicitement) sur tous les objets jetables. Toutefois, dans le scénario hypothétique où vous ne pouvez pas , cela dépend . sur la façon dont l'objet a été mis en œuvre.

En général, les objets .Net suivront le même schéma que ces lignes . Le modèle consiste à définir un finaliseur qui nettoie les choses au cas où il n’est pas appelé de disposer et ensuite supprime le finaliseur. Ce qui réduit la charge de mémoire et donne moins de travail au CPG.

Parmi les nombreux problèmes liés à l’appel de Dispose depuis le finaliseur, c’est que vous transformez un problème à thread unique en un problème multithread, le finaliseur s’exécutera sur un thread différent pouvant en exposer de manière très subtile et complexe. difficile d'attraper les insectes. En outre, cela signifie que vous conserverez des ressources non gérées plus longtemps que prévu (par exemple, vous ouvrez un fichier, oubliez d'appeler close ou mettez-le au rebut et la prochaine fois que vous l'ouvrirez, il est verrouillé).

En bout de ligne, c’est une meilleure pratique de disposer de tous les objets jetables, sinon vous pouvez introduire des bogues étranges et complexes. Toutefois, certains frameworks, tels que sharepoint, renvoient des instances d'objets partagés qui ne devraient pas être éliminés conformément à la documentation.

Je trouve généralement le code beaucoup plus lisible lorsque je mets mes objets avec l'option "using". modèle. Le problème avec l'appel explicite de Dispose (object.Dispose ()) est qu'il peut être difficile de localiser où l'objet a été alloué et qu'il est très facile de l'oublier. Vous ne pouvez pas oublier de fermer le crochet de la déclaration using, le compilateur va se plaindre :)

EDIT / GOTCHA

Selon la documentation MS , vous ne devriez pas appelez dispose sur des références pour partager des objets partagés qui sont retournés par GetContextSite. Donc, vous devriez faire très attention ici.

Voir cette réponse pour un motif de partage sécurisé à utiliser.

  

Cependant, si vous avez une référence à un   ressource partagée, comme lorsque le   objet est fourni par le   Méthode GetContextSite dans un composant WebPart,   n'utilisez aucune méthode pour fermer le   objet. Utiliser l'une ou l'autre méthode sur un   ressource partagée provoque un accès   Erreur de violation à se produire. Dans des scénarios   où vous avez une référence à un partage   ressource, laissez plutôt Windows   SharePoint Services ou votre portail   application gérer l'objet.

Autres conseils

Ce qui suit est plus idiomatique et se lit mieux:

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
}

Vous devez explicitement (ou implicitement via à l'aide de l'instruction ) appeler la méthode Dispose. Les autres raisons de scinder le code en plusieurs lignes sont les suivantes:

  • lisibilité
  • il est plus facile de déboguer

La méthode Dispose peut être exécutée dans le finaliseur, mais il est préférable de l'appeler vous-même.

Je suggère de scinder la ligne et d’utiliser Dispose. Si un objet implémente IDisposable, vous devez supposer qu’il doit être éliminé et, par conséquent, l’utiliser dans un bloc using.

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SpUser spUser = null;
    if (spWeb != null)
    {
        spUser = spWeb.SiteUsers[@"foo\bar"];
    }
}

En procédant ainsi, vous obtenez la suppression de l'objet et la gestion des erreurs dans votre appel OpenWeb () ouvrant une ressource externe.

Fuite de mémoire? Non, vous ne devriez pas vous en inquiéter, en supposant que l’implémentation d’IDisposable respecte les directives de la bibliothèque de classes, car la prochaine collecte de place devrait la nettoyer.

Cependant, cela révèle une erreur dans votre code, car vous ne gérez pas correctement la durée de vie des implémentations de IDisposable avec lesquelles vous travaillez (je développe sur cette question à http://www.caspershouse.com/post/A-Better-Implementation-Pattern-for-IDisposable .aspx ). Votre deuxième bloc de code est une bonne première étape, mais vous ne garantissez pas l'appel de Dispose si l'appel à SiteUsers échoue.

Votre code révisé ressemblerait à ceci:

// Get the site.
var contextSite = SPControl.GetContextSite(HttpContext.Current);

// Work with the web.
using (SPWeb web = contextSite.OpenWeb())
{
  // Get the user and work with it.
  SPUser spUser = web.SiteUsers[@"foo\bar"];
}

Comme certains l’ont dit jusqu’à présent: vous ne devez jamais disposer d’objets que vous ne créez pas. Ainsi, dans votre exemple, vous ne devriez pas disposer de l'objet SPWeb ou de l'objet SPSite!

Cela détruira l'objet SPRequest actuel. Il peut sembler que cela fonctionne toujours, mais si vous ajoutez par exemple de nouveaux composants WebPart ultérieurement ou si vous essayez d'ouvrir le volet d'outils de votre composant WebPart, vous obtiendrez toutes sortes d'erreurs étranges.

Comme cela a déjà été dit, il est indispensable de disposer des instances de SPWeb et de SPSite que vous créez vous-même (nouveau).

Cela peut être fait avec using () ou avec try / finally (ce qui est le cas de la manière dont une instruction using () apparaîtra dans votre code MSIL). Si vous utilisez try / finally, il est recommandé de rechercher la valeur null sur votre instance SPWeb / SPSite et de rechercher SPWeb en premier, car SPSite éliminera automatiquement votre site Web.

Une autre chose importante à retenir est que, lors de la mise en boucle de SPWebCollections comme AllWebs ou Webs, vous devez disposer vos sous-sites Web lorsque vous les parcourez. S'il y a beaucoup de sous-sites Web et que vous utilisez du matériel 32 bits avec un potentiel de mémoire limité, vous pouvez remplir votre mémoire avec des objets SPRequest très rapidement. Cela entraînera des performances médiocres, car votre pool d'applications se recyclera régulièrement.

Dans ce cas, il est également recommandé de ne pas combiner les appels comme vous le faites dans votre exemple de code. Il est difficile à lire et si vous travailliez avec un SPWeb dont vous devriez disposer, vous ne pourriez pas! Ce type de fuite de mémoire est le plus difficile à détecter, alors ne le faites pas ;-)

Je vous recommande le blog de Roger Lamb pour plus de détails: http://blogs.msdn.com/rogerla Quelques détails techniques sur le blog de Stefan Go: 227: http: // blogs. technet.com/stefan_gossner/archive/2008/12/05/disposing-spweb-and-spsite-objects.aspx

hth Anders

Beaucoup de réponses ici supposent qu’il est important que le nom Dispose soit appelé ultérieurement. Toutefois, lorsque vous travaillez avec SPSite et SPWeb, vous souhaitez absolument appeler Dispose () dès que vous le pouvez. Déterminer quand vous devriez est souvent délicat, mais il existe de nombreux bonnes références disponibles pour vous aider à répondre à cette question.

Pour expliquer pourquoi, Stefan Goer fournit un résumé succinct ici :

  

Chaque objet SPWeb et SPSite contient un   référence à un objet SPRequest qui   contient une référence à un COM SharePoint   objet qui est responsable de   communiquer avec le backend SQL   serveur.

     

La suppression d'un objet SPWeb ne sera pas   effectivement supprimer l'objet SPWeb de   mémoire (en fait, le framework .NET   ne permet de supprimer aucun objet   de mémoire d'une manière déterministe)   mais il appellera une méthode du SPWeb   objet qui provoque l'objet COM à   fermer la connexion au serveur SQL   et libérer sa mémoire allouée.

     

Cela signifie que la connexion au   serveur SQL backend restera ouvert   à partir du moment où l'objet SPRequest   a été créé jusqu'à l'objet SPWeb   est éliminé.

La meilleure pratique pour votre exemple de code serait la suivante:

SPSite contextSite = SPControl.GetContextSite(HttpContext.Current);
using (SPWeb spWeb = contextSite.OpenWeb())
{
  SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
  // Process spUser
}
// DO NOT use spUser
// DO NOT dispose site from HttpContext

Notez qu'il n'est pas sûr d'utiliser des objets SP tels que SPUser, SPList, etc. après la suppression de leur site Web parent.

De http://msdn.microsoft.com/en-us/ bibliothèque / aa973248.aspx Meilleures pratiques: utilisation d'objets Windows SharePoint Services jetables :

Si l'objet SPSite est obtenu à partir de SPControl.GetContextSite, l'application appelante ne doit PAS en disposer. Étant donné que les objets SPWeb et SPSite conservent une liste interne dérivée de cette manière, la suppression de l'objet peut entraîner un comportement imprévisible du modèle d'objet SharePoint.

Personne ne semble avoir encore ajouté ceci:

Si votre objet nécessite un éliminateur (c'est-à-dire qu'il prend les ressources à libérer), vous devez implémenter un finaliseur qui appelle la méthode de l'éliminateur.

Dans le destructeur, vous pouvez ajouter la ligne:

System.GC.SuppressFinalize(this)

Ce qui empêchera la finalisation si vous appelez le destructeur. De cette façon, vous pouvez utiliser votre objet correctement si nécessaire, tout en garantissant qu’il se nettoie lui-même via le finaliseur (c’est la raison pour laquelle C # dispose d’un finaliseur).

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