Définir des objets sur Null / Nothing après utilisation dans .NET

187

Devez-vous définir tous les objets sur null( Nothingdans VB.NET) une fois que vous en avez terminé avec eux?

Je comprends que dans .NET, il est essentiel de disposer de toutes les instances d'objets qui implémentent l' IDisposableinterface pour libérer certaines ressources bien que l'objet puisse toujours être quelque chose après avoir été éliminé (d'où la isDisposedpropriété dans les formulaires), donc je suppose qu'il peut toujours résider en mémoire ou du moins en partie?

Je sais aussi que lorsqu'un objet sort du champ d'application, il est alors marqué pour la collecte prêt pour le prochain passage du garbage collector (bien que cela puisse prendre du temps).

Donc, avec cela à l'esprit, le paramétrer pour nullaccélérer le système libérant la mémoire car il n'a pas à déterminer qu'il n'est plus dans la portée et y a-t-il des effets secondaires négatifs?

Les articles MSDN ne le font jamais dans les exemples et actuellement je le fais car je ne vois pas le mal. Cependant, j'ai rencontré un mélange d'opinions, donc tous les commentaires sont utiles.

John
la source
4
+1 excellente question. Est-ce que quelqu'un connaît une circonstance dans laquelle le compilateur optimisera complètement l'assignation? c'est-à-dire que quelqu'un a regardé MSIL dans des circonstances différentes et noté IL pour avoir mis un objet à null (ou son absence).
Tim Medora

Réponses:

73

Karl est tout à fait correct, il n'est pas nécessaire de définir les objets sur null après utilisation. Si un objet est implémenté IDisposable, assurez-vous simplement d'appeler IDisposable.Dispose()lorsque vous avez terminé avec cet objet (enveloppé dans un bloc try.. finallyou, un using()bloc). Mais même si vous ne vous souvenez pas d'appeler Dispose(), la méthode finaliser sur l'objet devrait vous appeler Dispose().

J'ai pensé que c'était un bon traitement:

Creuser dans IDisposable

et ça

Comprendre IDisposable

Il ne sert à rien d'essayer de remettre en question le GC et ses stratégies de gestion parce qu'il est auto-ajustable et opaque. Il y a eu une bonne discussion sur le fonctionnement interne avec Jeffrey Richter sur Dot Net Rocks ici: Jeffrey Richter sur le modèle de mémoire Windows et le livre Richters CLR via C # chapitre 20 a un excellent traitement:

Kev
la source
6
La règle de ne pas définir à null n'est pas "difficile et rapide" ... si l'objet est placé sur le tas d'objets volumineux (la taille est> 85K), cela aidera le GC si vous définissez l'objet sur null lorsque vous avez terminé En l'utilisant.
Scott Dorman
Je suis d'accord dans une certaine mesure, mais à moins que vous ne commenciez à ressentir une pression de mémoire, je ne vois pas la nécessité d '«optimiser prématurément» en définissant les objets sur null après utilisation.
Kev
21
Toute cette affaire de "ne pas optimiser prématurément" ressemble plus à "Préférez lent et ne vous inquiétez pas parce que les processeurs deviennent plus rapides et les applications CRUD n'ont pas besoin de vitesse de toute façon." C'est peut-être juste moi. :)
BobbyShaftoe
19
Ce que cela signifie vraiment, c'est que "Le Garbage Collector est meilleur que vous pour gérer la mémoire". Cela pourrait être juste moi. :)
BobRodes
2
@BobbyShaftoe: C'est probablement aussi faux de dire "l'optimisation prématurée est mauvaise, toujours" que de sauter à l'extrême opposé de "sonne plus comme" préfère lent "." Aucun programmeur raisonnable ne le dirait non plus. Il s'agit de nuances et d'être intelligent sur ce que vous optimisez. Je m'inquiéterais personnellement de la clarté du code et ALORS RÉELLEMENT TESTER les performances, car j'ai personnellement vu beaucoup de gens (y compris moi-même quand j'étais plus jeune) passer beaucoup trop de temps à créer l'algorithme "parfait", pour gagner 0,1 ms en 100 000 itérations, alors que la lisibilité était complètement tirée.
Brent Rittenhouse
36

Une autre raison d'éviter de définir des objets sur null lorsque vous en avez terminé avec eux est que cela peut en fait les garder en vie plus longtemps.

par exemple

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

permettra à l'objet référencé par someType d'être GC après l'appel à "DoSomething" mais

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

peut parfois garder l'objet en vie jusqu'à la fin de la méthode. Le JIT optimisera généralement l'assignation à null , de sorte que les deux bits de code finissent par être identiques.

Wilka
la source
C'est un point intéressant. J'ai toujours pensé que les objets ne sortent pas du champ d'application tant que la méthode dans laquelle ils sont étendus n'est pas terminée. À moins bien sûr que l'objet ne soit défini dans un bloc Using ou soit explicitement défini sur Nothing ou null.
Guru Josh
1
La meilleure façon de s'assurer qu'ils restent en vie est d'utiliser GC.KeepAlive(someType); See ericlippert.com/2013/06/10/construction-destruction
NotMe
7

Aussi:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of
Steve Tranby
la source
7

En général, il n'est pas nécessaire d'annuler les objets après utilisation, mais dans certains cas, je trouve que c'est une bonne pratique.

Si un objet implémente IDisposable et est stocké dans un champ, je pense qu'il est bon de l'annuler, juste pour éviter d'utiliser l'objet supprimé. Les bogues du type suivant peuvent être douloureux:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Il est bon d'annuler le champ après l'avoir supprimé et d'obtenir un NullPtrEx juste à la ligne où le champ est à nouveau utilisé. Sinon, vous pourriez rencontrer un bug cryptique sur toute la ligne (en fonction de ce que fait exactement DoSomething).

dbkk
la source
8
Eh bien, un objet supprimé doit lancer ObjectDisposedException s'il a déjà été supprimé. Pour autant que je sache, cela nécessite un code passe-partout, mais là encore, Disposed est de toute façon un paradigme mal pensé.
nicodemus13
3
Ctrl + F pour .Dispose(). Si vous le trouvez, vous n'utilisez pas correctement IDisposable. La seule utilisation d'un objet jetable doit être dans les limites d'un bloc d'utilisation. Et après le bloc d'utilisation, vous n'y avez même plus accès myField. Et dans le bloc using, le réglage sur nulln'est pas obligatoire, le bloc using supprimera l'objet pour vous.
Suamere
7

Il y a de fortes chances que votre code ne soit pas suffisamment structuré si vous ressentez le besoin de nullvariables.

Il existe plusieurs façons de limiter la portée d'une variable:

Comme mentionné par Steve Tranby

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

De même, vous pouvez simplement utiliser des accolades:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Je trouve que l'utilisation d'accolades sans aucun "titre" pour vraiment nettoyer le code et aider à le rendre plus compréhensible.

mbillard
la source
J'ai essayé une fois d'utiliser des portées locales personnalisées (principalement en tant que smarta $$). L'entreprise a explosé.
Suamere
Sur une autre note: c'est parce que le compilateur c # trouvera des variables de portée locale qui implémentent IDisposable, et appellera .Dispose (la plupart du temps) lorsque leur portée se termine. Cependant ... Les connexions SQL sont un moment important où .Dispose () n'est jamais optimisé. Certains types nécessitent une attention explicite, donc je fais toujours les choses de manière explicite pour ne pas me faire mordre.
Suamere
5

Le seul moment où vous devez définir une variable sur null est lorsque la variable ne sort pas du champ d'application et que vous n'avez plus besoin des données qui lui sont associées. Sinon, il n'y a pas besoin.

Bob
la source
2
C'est vrai, mais cela signifie aussi que vous devriez probablement refactoriser votre code. Je ne pense pas avoir jamais eu besoin de déclarer une variable en dehors de sa portée prévue.
Karl Seguin
2
Si "variable" est compris comme incluant des champs d'objet, alors cette réponse a beaucoup de sens. Dans le cas où "variable" signifie seulement "variable locale" (d'une méthode), alors nous parlons probablement de cas de niche ici (par exemple une méthode qui fonctionne pendant une période beaucoup plus longue que d'habitude).
stakx - ne contribue plus
5

En général, pas besoin de définir sur null. Mais supposons que vous ayez une fonctionnalité de réinitialisation dans votre classe.

Ensuite, vous pouvez le faire, car vous ne souhaitez pas appeler dispose deux fois, car une partie de Dispose peut ne pas être implémentée correctement et lever l'exception System.ObjectDisposed.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }
Munish Goyal
la source
Il est préférable de suivre cela avec un drapeau séparé peut-être.
Thulani Chivandikwa
3

ce type de "il n'est pas nécessaire de définir des objets à null après utilisation" n'est pas entièrement précis. Vous devez parfois NULL la variable après l'avoir supprimée.

Oui, vous devriez TOUJOURS appeler .Dispose()ou .Close()sur tout ce qui l'a lorsque vous avez terminé. Qu'il s'agisse de descripteurs de fichiers, de connexions de bases de données ou d'objets jetables.

Le modèle très pratique de LazyLoad est séparé de cela.

Dites que j'ai et instancié ObjAde class A. Class Aa une propriété publique appelée PropBdeclass B .

En interne, PropButilise la variable privée de _Bet par défaut à null. Quand PropB.Get()est utilisé, il vérifie si _PropBest nul et si c'est le cas, ouvre les ressources nécessaires pour instancier un Bdans _PropB. Il revient ensuite _PropB.

D'après mon expérience, c'est une astuce vraiment utile.

Là où le besoin de null entre en jeu, c'est si vous réinitialisez ou modifiez A d'une manière dont le contenu de _PropBétait l'enfant des valeurs précédentes de A, vous devrez Dispose AND null out _PropBafin que LazyLoad puisse réinitialiser pour récupérer la bonne valeur SI le code l'exige.

Si vous ne le faites _PropB.Dispose()et que peu de temps après, vous vous attendez à ce que la vérification null pour LazyLoad réussisse, elle ne sera pas nulle et vous examinerez des données périmées. En effet, vous devez l'annuler aprèsDispose() juste pour être sûr.

J'aurais bien aimé qu'il en soit autrement, mais j'ai actuellement du code présentant ce comportement après un Dispose()sur un _PropBet en dehors de la fonction d'appel qui a fait le Dispose (et donc presque hors de portée), le prop privé n'est toujours pas nul, et les données périmées sont toujours là.

Finalement, la propriété supprimée sera annulée, mais cela n'a pas été déterministe de mon point de vue.

La raison principale, comme dbkk y fait allusion, est que le conteneur parent ( ObjAavec PropB) garde l'instance de _PropBdans la portée, malgré le Dispose().

KenF
la source
Bon exemple montrant comment la définition manuelle de null signifie une erreur plus fatale pour l'appelant, ce qui est une bonne chose.
roule le
1

Dans certains cas, il est logique d'utiliser des références nulles. Par exemple, lorsque vous écrivez une collection - comme une file d'attente prioritaire - et par votre contrat, vous ne devriez pas garder ces objets en vie pour le client après que le client les a supprimés de la file d'attente.

Mais ce genre de chose n'a d'importance que dans les collections de longue date. Si la file d'attente ne survivra pas à la fin de la fonction dans laquelle elle a été créée, cela importe beaucoup moins.

Dans l'ensemble, vous ne devriez vraiment pas vous inquiéter. Laissez le compilateur et GC faire leur travail pour que vous puissiez faire le vôtre.

Patrick
la source
1

Jetez également un œil à cet article: http://www.codeproject.com/KB/cs/idisposable.aspx

Pour la plupart, la définition d'un objet sur null n'a aucun effet. La seule fois où vous devez vous assurer de le faire, c'est si vous travaillez avec un "gros objet", dont la taille est supérieure à 84 Ko (comme les bitmaps).

Scott Dorman
la source
1

Stephen Cleary explique très bien dans cet article: Dois-je définir des variables sur Null pour aider le nettoyage de la mémoire?

Dit:

La réponse courte, pour l'Impatient Oui, si la variable est un champ statique, ou si vous écrivez une méthode énumérable (en utilisant yield return) ou une méthode asynchrone (en utilisant async et await). Sinon, non.

Cela signifie que dans les méthodes régulières (non énumérables et non asynchrones), vous ne définissez pas les variables locales, les paramètres de méthode ou les champs d'instance sur null.

(Même si vous implémentez IDisposable.Dispose, vous ne devez toujours pas définir les variables sur null).

La chose importante à considérer est les champs statiques .

Les champs statiques sont toujours des objets racine , ils sont donc toujours considérés comme «vivants» par le garbage collector. Si un champ statique fait référence à un objet qui n'est plus nécessaire, il doit être défini sur null afin que le garbage collector le traite comme éligible pour la collecte.

La définition de champs statiques sur null n'a pas de sens si l'ensemble du processus est en cours d'arrêt. Le tas entier est sur le point d'être récupéré à ce stade, y compris tous les objets racine.

Conclusion:

Champs statiques ; C'est à peu près ça. Tout le reste est une perte de temps .

Eblème
la source
0

Je crois que de par la conception des implémenteurs GC, vous ne pouvez pas accélérer GC avec l'annulation. Je suis sûr qu'ils préféreraient que vous ne vous inquiétiez pas de comment / quand GC fonctionne - traitez-le comme cet être omniprésent protège et veille sur vous ... (s'incline la tête en bas, lève le poing vers le ciel) .. .

Personnellement, je mets souvent explicitement des variables à null lorsque j'en ai fini avec elles comme une forme d'auto-documentation. Je ne déclare pas, n'utilise pas, puis ne mets pas à null plus tard - je null immédiatement après qu'ils ne soient plus nécessaires. Je dis, explicitement, "J'en ai officiellement fini avec toi ... sois parti ..."

L'annulation est-elle nécessaire dans une langue du GC? Non. Est-ce utile pour le GC? Peut-être que oui, peut-être non, je ne sais pas avec certitude, de par sa conception, je ne peux vraiment pas le contrôler, et quelle que soit la réponse d'aujourd'hui avec cette version ou celle, les futures implémentations de GC pourraient changer la réponse hors de mon contrôle. De plus, si / quand l'annulation est optimisée, ce n'est guère plus qu'un commentaire sophistiqué si vous voulez.

Je pense que si cela rend mon intention plus claire pour le prochain pauvre imbécile qui suit mes traces, et si cela «pourrait» potentiellement aider GC parfois, alors cela en vaut la peine pour moi. Surtout, cela me fait me sentir bien rangé et clair, et Mongo aime se sentir bien rangé et clair. :)

Je le regarde comme ceci: les langages de programmation existent pour permettre aux gens de donner à d'autres personnes une idée de l'intention et au compilateur une demande de travail sur ce qu'il faut faire - le compilateur convertit cette demande dans un langage différent (parfois plusieurs) pour un processeur - le (s) CPU (s) pourrait (s) donner une idée de la langue que vous avez utilisée, des paramètres de votre onglet, des commentaires, des accents stylistiques, des noms de variables, etc. Beaucoup de choses écrites dans le code ne sont pas converties en ce qui est consommé par le processeur dans l'ordre que nous avons spécifié. Notre C, C ++, C #, Lisp, Babel, assembleur ou tout ce qui est de la théorie plutôt que de la réalité, écrit comme un énoncé de travail. Ce que vous voyez n'est pas ce que vous obtenez, oui, même en langage assembleur.

Je comprends que l'état d'esprit des «choses inutiles» (comme les lignes vides) «ne sont rien d'autre que du bruit et du code encombrant». C'était moi plus tôt dans ma carrière; Je comprends totalement cela. À ce stade, je me penche vers ce qui rend le code plus clair. Ce n'est pas comme si j'ajoutais même 50 lignes de «bruit» à mes programmes - c'est quelques lignes ici ou là.

Il existe des exceptions à toute règle. Dans les scénarios avec mémoire volatile, mémoire statique, conditions de course, singletons, utilisation de données "périmées" et tout ce genre de pourriture, c'est différent: vous DEVEZ gérer votre propre mémoire, en verrouillant et en annulant à propos car la mémoire ne fait pas partie de l'univers GC'd - j'espère que tout le monde comprend cela. Le reste du temps, avec les langages GC, c'est une question de style plutôt que de nécessité ou une amélioration garantie des performances.

À la fin de la journée, assurez-vous de comprendre ce qui est éligible pour GC et ce qui ne l'est pas; verrouiller, éliminer et annuler de manière appropriée; cirer, cirer; Inspire, expire; et pour tout le reste je dis: si ça fait du bien, fais-le. Votre kilométrage peut varier ... comme il se doit ...

John
la source
0

Je pense que remettre quelque chose à zéro est compliqué. Imaginez un scénario où l'élément défini sur now est exposé, par exemple, via une propriété. Maintenant, si un morceau de code utilise accidentellement cette propriété après la suppression de l'élément, vous obtiendrez une exception de référence nulle qui nécessite une enquête pour déterminer exactement ce qui se passe.

Je pense que les jetables de framework permettront de lancer ObjectDisposedException, ce qui est plus significatif. Ne pas les remettre à zéro serait mieux alors pour cette raison.

Thulani Chivandikwa
la source
-1

Certains objets supposent la .dispose()méthode qui force la suppression de la ressource de la mémoire.

GateKiller
la source
11
Non, ce n'est pas le cas; Dispose () ne collecte pas l'objet - il est utilisé pour effectuer un nettoyage déterministe, libérant généralement des ressources non gérées.
Marc Gravell
1
Gardant à l'esprit que le déterminisme ne s'applique qu'aux ressources gérées, pas aux ressources non gérées (c'est-à-dire à la mémoire)
nicodemus13