C # 2008
J'y travaille depuis un certain temps maintenant, et je suis toujours confus quant à l'utilisation des méthodes de finalisation et d'élimination dans le code. Mes questions sont ci-dessous:
Je sais que nous n'avons besoin que d'un finaliseur pour disposer de ressources non gérées. Cependant, s'il existe des ressources gérées qui appellent des ressources non gérées, aurait-il encore besoin d'implémenter un finaliseur?
Cependant, si je développe une classe qui n'utilise aucune ressource non gérée - directement ou indirectement, dois-je implémenter le
IDisposable
pour autoriser les clients de cette classe à utiliser «l'instruction using»?Serait-il possible d'implémenter IDisposable juste pour permettre aux clients de votre classe d'utiliser l'instruction using?
using(myClass objClass = new myClass()) { // Do stuff here }
J'ai développé ce code simple ci-dessous pour démontrer l'utilisation de Finaliser / éliminer:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
Question sur le code source:
Ici, je n'ai pas ajouté le finaliseur, et normalement le finaliseur sera appelé par le GC, et le finaliseur appellera Dispose. Comme je n'ai pas le finaliseur, quand dois-je appeler la méthode Dispose? Est-ce le client de la classe qui doit l'appeler?
Donc ma classe dans l'exemple s'appelle NoGateway et le client pourrait utiliser et éliminer la classe comme ceci:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
La méthode Dispose serait-elle automatiquement appelée lorsque l'exécution atteindrait la fin du bloc using, ou le client doit-il appeler manuellement la méthode Dispose? c'est à dire
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
J'utilise la
WebClient
classe dans maNoGateway
classe. ParceWebClient
qu'implémente l'IDisposable
interface, cela signifie-t-il queWebClient
indirectement utilise des ressources non gérées? Existe-t-il une règle stricte et rapide pour suivre cela? Comment savoir qu'une classe utilise des ressources non managées?
la source
Réponses:
Le modèle IDisposable recommandé est ici . Lors de la programmation d'une classe qui utilise IDisposable, vous devez généralement utiliser deux modèles:
Lorsque vous implémentez une classe scellée qui n'utilise pas de ressources non managées, vous implémentez simplement une méthode Dispose comme avec les implémentations d'interface normales:
Lorsque vous implémentez une classe non scellée, procédez comme suit:
Notez que je n'ai pas déclaré de finaliseur dans
B
; vous ne devez implémenter un finaliseur que si vous disposez de ressources réelles non gérées à éliminer. Le CLR traite les objets finalisables différemment des objets non finalisables, même s'ilSuppressFinalize
est appelé.Donc, vous ne devez pas déclarer un finaliseur sauf si vous le devez, mais vous donnez aux héritiers de votre classe un hook pour appeler votre
Dispose
et implémenter un finaliseur eux-mêmes s'ils utilisent directement des ressources non managées:Si vous n'utilisez pas directement des ressources non gérées (
SafeHandle
et que les amis ne comptent pas, car ils déclarent leurs propres finaliseurs), n'implémentez pas de finaliseur, car le GC traite les classes finalisables différemment, même si vous supprimez plus tard le finaliseur. Notez également que, même s'ilB
n'a pas de finaliseur, il appelle toujoursSuppressFinalize
pour traiter correctement les sous-classes qui implémentent un finaliseur.Lorsqu'une classe implémente l'interface IDisposable, cela signifie qu'il existe quelque part des ressources non gérées dont vous devez vous débarrasser lorsque vous avez fini d'utiliser la classe. Les ressources réelles sont encapsulées dans les classes; vous n'avez pas besoin de les supprimer explicitement. Un simple appel
Dispose()
ou encapsulation de la classe dans ausing(...) {}
vous permettra de vous débarrasser de toutes les ressources non gérées si nécessaire.la source
IDisposable
, il y a de fortes chances que cela traîne pendant un certain temps de toute façon. Vous économisez le CLR l'effort d'avoir à le copier de Gen0 -> Gen1 -> Gen2Le modèle officiel à mettre en œuvre
IDisposable
est difficile à comprendre. Je crois que celui-ci est meilleur :Une solution encore meilleure consiste à avoir une règle selon laquelle vous devez toujours créer une classe wrapper pour toute ressource non managée que vous devez gérer:
Avec
SafeHandle
et ses dérivés, ces classes devraient être très rares .Le résultat pour les classes jetables qui ne traitent pas directement avec les ressources non managées, même en présence d'héritage, est puissant: elles n'ont plus besoin de se préoccuper des ressources non managées . Ils seront simples à mettre en œuvre et à comprendre:
la source
disposed
indicateur et vérifier en conséquence.disposed
indicateur, de le vérifier avant de le supprimer et de le définir après sa suppression. Regardez ici pour l'idée. Vous devez également vérifier l'indicateur avant toutes les méthodes de la classe. Logique? C'est compliqué?Notez que toute mise en œuvre IDisposable doit suivre le modèle ci-dessous (à mon humble avis). J'ai développé ce modèle sur la base des informations provenant de plusieurs excellents «dieux» .NET des directives de conception du .NET Framework (notez que MSDN ne suit pas cela pour une raison quelconque!). Les lignes directrices de conception du .NET Framework ont été écrites par Krzysztof Cwalina (architecte CLR à l'époque) et Brad Abrams (je crois que le responsable du programme CLR à l'époque) et Bill Wagner ([Effective C #] et [More Effective C #] (prenez simplement un recherchez-les sur Amazon.com:
Notez que vous ne devez JAMAIS implémenter un Finalizer à moins que votre classe ne contienne directement (et n'hérite pas) des ressources UNmanaged. Une fois que vous implémentez un Finalizer dans une classe, même s'il n'est jamais appelé, il est garanti de vivre pour une collection supplémentaire. Il est automatiquement placé dans la file d'attente de finalisation (qui s'exécute sur un seul thread). De plus, une note très importante ... tout le code exécuté dans un Finalizer (si vous devez en implémenter un) DOIT être thread-safe ET exception-safe! MAUVAISES choses se produiront autrement ... (c'est-à-dire un comportement indéterminé et dans le cas d'une exception, un plantage fatal irrécupérable de l'application).
Le modèle que j'ai mis en place (et écrit un extrait de code pour) suit:
Voici le code pour implémenter IDisposable dans une classe dérivée. Notez que vous n'avez pas besoin de répertorier explicitement l'héritage d'IDisposable dans la définition de la classe dérivée.
J'ai publié cette implémentation sur mon blog à: Comment implémenter correctement le modèle de suppression
la source
Interlocked.Exchange
unIsDisposed
indicateur sur un entier dans la fonction wrapper non virtuel serait plus sûre.Je suis d' accord avec pm100 (et j'aurais dû le dire explicitement dans mon post précédent).
Vous ne devez jamais implémenter IDisposable dans une classe à moins d'en avoir besoin. Pour être très précis, il y a environ 5 fois où vous auriez besoin / devriez implémenter IDisposable:
Votre classe contient explicitement (c'est-à-dire pas via l'héritage) toutes les ressources gérées qui implémentent IDisposable et doivent être nettoyées une fois que votre classe n'est plus utilisée. Par exemple, si votre classe contient une instance d'un Stream, DbCommand, DataTable, etc.
Votre classe contient explicitement toutes les ressources gérées qui implémentent une méthode Close () - par exemple IDataReader, IDbConnection, etc. Notez que certaines de ces classes implémentent IDisposable en ayant Dispose () ainsi qu'une méthode Close ().
Votre classe contient explicitement une ressource non gérée - par exemple un objet COM, des pointeurs (oui, vous pouvez utiliser des pointeurs en C # géré mais ils doivent être déclarés dans des blocs "non sécurisés", etc. Dans le cas de ressources non gérées, vous devez également vous assurer de appelez System.Runtime.InteropServices.Marshal.ReleaseComObject () sur le RCW. Même si le RCW est, en théorie, un wrapper managé, il y a toujours un comptage de références en cours sous les couvertures.
Si votre classe s'abonne à des événements en utilisant des références fortes. Vous devez vous désinscrire / vous détacher des événements. Assurez-vous toujours que ceux-ci ne sont pas nuls avant d'essayer de les désenregistrer / les détacher!.
Votre classe contient toute combinaison des éléments ci-dessus ...
Une alternative recommandée à l'utilisation d'objets COM et à l'utilisation de Marshal.ReleaseComObject () consiste à utiliser la classe System.Runtime.InteropServices.SafeHandle.
La BCL (Base Class Library Team) a un bon blog à ce sujet ici http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Une note très importante à faire est que si vous travaillez avec WCF et que vous nettoyez des ressources, vous devriez PRESQUE TOUJOURS éviter le bloc "using". Il existe de nombreux articles de blog et certains sur MSDN expliquant pourquoi c'est une mauvaise idée. J'ai également posté à ce sujet ici - N'utilisez pas 'using ()' avec un proxy WCF
la source
Utilisation de lambdas au lieu d'IDisposable.
Je n'ai jamais été ravi de l'idée d'utiliser / IDisposable. Le problème est qu'il nécessite que l'appelant:
Ma nouvelle méthode préférée consiste à utiliser une méthode d'usine et un lambda à la place
Imaginez que je veux faire quelque chose avec un SqlConnection (quelque chose qui devrait être enveloppé dans une utilisation). Classiquement, vous feriez
Nouvelle façon
Dans le premier cas, l'appelant ne pouvait tout simplement pas utiliser la syntaxe using. Dans le second cas, l'utilisateur n'a pas le choix. Aucune méthode ne crée un objet SqlConnection, l'appelant doit appeler DoWithConnection.
DoWithConnection ressemble à ceci
MakeConnection
est maintenant privéla source
DoForAll(Action<T>) where T:IComparable<T>
, en appelant le délégué indiqué sur chaque enregistrement. Étant donné deux de ces objets, qui renverront tous deux des données dans un ordre trié, comment sortirait-on tous les éléments qui existent dans une collection mais pas l'autre? Si les types sont implémentésIEnumerable<T>
, on pourrait effectuer une opération de fusion, mais cela ne fonctionnera pas avecDoForAll
.DoForAll
collections sans avoir à copier d'abord une, dans son intégralité, dans une autre structure, serait d'utiliser deux threads, ce qui serait plutôt plus lourd de ressources que d'utiliser simplement quelques IEnumerable et en faisant attention pour les libérer.personne n'a répondu à la question de savoir si vous devez implémenter IDisposable même si vous n'en avez pas besoin.
Réponse courte: Non
Longue réponse:
Cela permettrait à un consommateur de votre classe d'utiliser «using». La question que je poserais est - pourquoi le feraient-ils? La plupart des développeurs n'utiliseront pas «à l'aide» à moins qu'ils ne sachent qu'ils doivent - et comment savent-ils. Soit
Donc, en implémentant IDisposable, vous dites aux développeurs (au moins certains) que cette classe termine quelque chose qui doit être publié. Ils utiliseront «using» - mais il y a d'autres cas où l'utilisation n'est pas possible (la portée de l'objet n'est pas locale); et ils devront commencer à s'inquiéter de la durée de vie des objets dans ces autres cas - je m'inquiéterais à coup sûr. Mais ce n'est pas nécessaire
Vous implémentez Idisposable pour leur permettre d'utiliser en utilisant, mais ils n'utiliseront pas en utilisant à moins que vous leur dites.
Alors ne le fais pas
la source
Si vous utilisez d'autres objets gérés qui utilisent des ressources non gérées, il n'est pas de votre responsabilité de vous assurer qu'ils sont finalisés. Votre responsabilité est d'appeler Dispose sur ces objets lorsque Dispose est appelé sur votre objet, et cela s'arrête là.
Si votre classe n'utilise pas de ressources rares, je ne vois pas pourquoi vous voudriez que votre classe implémente IDisposable. Vous ne devez le faire que si vous êtes:
Oui, le code qui utilise votre code doit appeler la méthode Dispose de votre objet. Et oui, le code qui utilise votre objet peut utiliser
using
comme vous l'avez montré.(2 encore?) Il est probable que le WebClient utilise des ressources non managées ou d'autres ressources managées qui implémentent IDisposable. La raison exacte, cependant, n'est pas importante. Ce qui est important, c'est qu'il implémente IDisposable, et il vous incombe donc d'agir sur cette connaissance en éliminant l'objet lorsque vous en avez terminé, même s'il s'avère que WebClient n'utilise aucune autre ressource.
la source
Éliminer le motif:
Exemple d'héritage:
la source
Certains aspects d' une autre réponse sont légèrement incorrects pour 2 raisons:
Première,
est en fait équivalent à:
Cela peut sembler ridicule, car le «nouvel» opérateur ne doit jamais retourner «null», sauf si vous avez une exception OutOfMemory. Mais considérez les cas suivants: 1. Vous appelez une FactoryClass qui renvoie une ressource IDisposable ou 2. Si vous avez un type qui peut ou non hériter d'IDisposable en fonction de son implémentation - rappelez-vous que j'ai vu le modèle IDisposable implémenté incorrectement beaucoup parfois sur de nombreux clients où les développeurs ajoutent simplement une méthode Dispose () sans hériter d'IDisposable (mauvais, mauvais, mauvais). Vous pouvez également avoir le cas d'une ressource IDisposable renvoyée par une propriété ou une méthode (encore une fois mauvaise, mauvaise, mauvaise - ne donnez pas vos ressources IDisposable)
Si l'opérateur «as» renvoie null (ou une propriété ou une méthode renvoyant la ressource) et que votre code dans le bloc «using» protège contre «null», votre code ne explosera pas lorsque vous tenterez d'appeler Dispose sur un objet null en raison de la vérification nulle «intégrée».
La deuxième raison pour laquelle votre réponse n'est pas exacte est due au stmt suivant:
Premièrement, la finalisation (ainsi que le GC lui-même) n'est pas déterministe. Le CLR détermine quand il appellera un finaliseur. c'est-à-dire que le développeur / code n'a aucune idée. Si le modèle IDisposable est correctement implémenté (comme je l'ai indiqué ci-dessus) et que GC.SuppressFinalize () a été appelé, le Finalizer ne sera PAS appelé. C'est l'une des grandes raisons de mettre correctement en œuvre le modèle correctement. Puisqu'il n'y a qu'un seul thread Finalizer par processus géré, quel que soit le nombre de processeurs logiques, vous pouvez facilement dégrader les performances en sauvegardant ou même en suspendant le thread Finalizer en oubliant d'appeler GC.SuppressFinalize ().
J'ai publié une implémentation correcte du modèle Dispose sur mon blog: Comment implémenter correctement le modèle Dispose
la source
NoGateway = new NoGateway();
etNoGateway != null
?1) WebClient est un type géré, vous n'avez donc pas besoin d'un finaliseur. Le finaliseur est nécessaire dans le cas où vos utilisateurs ne suppriment pas () de votre classe NoGateway et le type natif (qui n'est pas collecté par le GC) doit être nettoyé après. Dans ce cas, si l'utilisateur n'appelle pas Dispose (), le WebClient contenu sera supprimé par le GC juste après le NoGateway.
2) Indirectement oui, mais vous ne devriez pas vous en soucier. Votre code est correct en l'état et vous ne pouvez pas empêcher vos utilisateurs d'oublier de Dispose () très facilement.
la source
Modèle de msdn
la source
est équivalent à
Un finaliseur est appelé lorsque le GC détruit votre objet. Cela peut être à un moment totalement différent de celui où vous quittez votre méthode. Le Dispose of IDisposable est appelé immédiatement après avoir quitté le bloc using. Par conséquent, le modèle est généralement utilisé pour utiliser pour libérer des ressources immédiatement après que vous n'en ayez plus besoin.
la source
D'après ce que je sais, il est fortement recommandé de ne PAS utiliser le Finalizer / Destructor:
Surtout, cela est dû au fait de ne pas savoir quand ni SI il sera appelé. La méthode d'élimination est bien meilleure, surtout si vous nous utilisez ou éliminez directement.
l'utilisation est bonne. utilise le :)
la source