12
votes

Héritage et destructeurs en C #

Selon Ce , il indique que Les destructeurs ne peuvent être hérités ni surchargés. dans mon cas, pour toutes les sous-classes, les destructeurs seront identiques. Est-ce à peu près me dire que je dois définir le même destructeur dans chaque sous-classe. Il n'y a aucun moyen de pouvoir déclarer le destructeur dans la classe de base et avoir la poignée de la destruction? Dis que j'ai quelque chose comme ça: xxx

lorsque B est détruit, son destructeur ne sera pas appelé?


4 commentaires

Semble être quelque chose qui serait assez facile à tester. Avez-vous des exemples où le destructeur pour B n'est pas exécuté?


@Yuck Eh bien cela me conduit à une autre question stupide ... "Quand les destructeurs sont-ils appelés réellement? Est-ce sur la collecte des ordures, lorsque la variable tombe hors de portée?


C'est une question différente. Je vous encourage à le poster comme tel, mais on lui a demandé si souvent ... Longue histoire courte: sur la collecte des ordures, mais cela peut se produire beaucoup plus tard ou pas du tout < / i>.


Mieux se lire sur les destructeurs et pourquoi pas pour les utiliser. Je soupçonne que tu n'as pas besoin d'un du tout.


7 Réponses :


7
votes

Le finaliseur que vous avez défini dans un sera appelé lorsqu'une instance de B est détruite.

Si vous définissez un finaliseur dans A et B, le finisseur le plus spécifique (B) fonctionnera en premier, puis le moins spécifique (A).


0 commentaires

1
votes

Si vous définissez un destructeur pour B, il sera appelé, suivi de A. Voir le exemple au bas du lien que vous avez fourni .


0 commentaires

18
votes

Ce n'est pas un destructeur en C #. Il est connu comme un finializer; et quand il est appelé est non déterministe. Vous ne pouvez pas compter sur cela pour être appelé du tout.

Les finaliseurs sont utilisés en dernier recours pour nettoyer les ressources non gérées. Vous devriez examiner le Disposez le modèle .


6 commentaires

Je seconde la notion d'utilisation isisposable dans la plupart de ces situations


Techniquement, la spécification C # appelle cela un destructeur. (Personnellement, je pense que c'est un nom trompeur et cela devrait s'appeler finaliseur même dans la spécification C #)


Même Eric Lippert se lamentait que c'est appelé un destructeur dans la spécification.


@Codeinchaos: Je suis d'accord, mais nous sommes bloqués avec cela maintenant.


Indiquant "ce n'est pas un destructeur en C #" ne contribue pas.


Un "destructeur" est une construction C # qui remplace la finalisation () avec une méthode qui enveloppe le bit de code fourni et appelle la base.Finalize (). Le terme "destructeur" n'ajoute que de la confusion, et la construction C # n'ajoute aucune valeur discernable sur tout simplement non pas interdisant les substituts de objet.finalize , mais le terme est ce qu'il est, et il n'y a pas d'autre terme que Fait référence précisément à l'entité syntaxique qui est écrite à l'aide de la séquence destructeurs, ni au code de wrapper idiot qui est généré par cette syntaxe.



44
votes

Selon cela, il indique que les destructeurs ne peuvent être hérités ni surchargés.

correct. Les destructeurs ne sont pas des membres héritables et ne sont pas virtuels et ne peuvent donc pas être remplacés. Ils ont toujours la même signature afin qu'ils ne puissent pas être surchargés.

Dans mon cas, pour toutes les sous-classes, les destructeurs seront identiques.

Le fait que vous posiez une question aussi fondamentale me dise que vous ne devriez pas mettre en œuvre un destructeur en premier lieu. Mise en œuvre d'un destructeur correctement est l'une des choses les plus difficiles à faire en C # dans tous les cas les plus triviaux. Pourquoi croyez-vous que vous devez mettre en œuvre un destructeur?

Est-ce à peu près me dire que je dois définir le même destructeur de chaque sous-classe?

Non, pas du tout. Comment êtes-vous arrivé à cette conclusion du fait que lestructeurs ne sont pas hérités?

Il n'y a aucun moyen de pouvoir déclarer le destructeur dans la classe de base et avoir la poignée de la destruction?

Bien sûr, c'est une chose raisonnable à faire, à condition que vous soyez plié pour mettre en œuvre un destructeur en premier lieu.

Quand B est détruit, son destructeur ne sera pas appelé?

c'est incorrect.

Il me semble que cela vous aurait pris beaucoup de temps pour l'essayer vous-même que de poser la question ici et d'attendre une réponse.

Quand les destructeurs sont-ils réellement appelés? Est-ce sur la collecte des ordures, lorsque la variable tombe hors de portée?

Ma précédente conjecture est correcte. Vous ne devez certainement pas mettre en œuvre un destructeur tant que vous comprenez profondément le processus de collecte des ordures. Le fait que vous croyiez que les variables sont collectées lorsqu'elles tombent hors de portée, par exemple, indique que vous ne le faites pas comprendre cela assez profondément pour écrire un destructeur correct.

Lorsqu'un objet est déterminé à être inacceptable à partir d'une racine GC par le collecteur et que l'objet a un finaliseur qui n'a pas été supprimé, l'objet est favorisé à la prochaine génération en le plaçant sur la file d'attente de finalisation pour la mise en service par la Fil finisseur. Sinon, sa mémoire est récupérée.

Lorsque le fil de finalisation se déplace pour courir, il exécute tous les destructeurs de l'objet. (Les destructeurs courront dans l'ordre de la plupart dérivés au moins dérivés.) Après ce processus, l'objet peut alors ou non être inaccessible et la finalisation peut ou non être supprimée. Si l'objet est déterminé à être inaccessible, l'ensemble du processus recommence.

Je ne peux pas insister suffisamment sur la mesure où vous avez besoin de comprendre le processus de GC afin de le faire correctement. Lorsque vous écrivez un destructeur, il circule dans un environnement où rien n'a de sens . Toutes les références de l'objet peuvent être des objets qui ne sont que enracinés par la file d'attente finaliseur; Normalement, toutes les références sont de vivre des choses. Les références peuvent être des objets déjà finalisés. Destructeurs fonctionnent sur un fil différent. Les destructeurs fonctionnent même si le constructeur a échoué, l'objet pourrait même pas être construit correctement . Les champs de types de valeur non atomique peuvent être partiellement écrits - il est tout à fait possible qu'un double champ ne comporte que quatre de ses octets définis par le constructeur lorsque le thread est abandonné; Le finaliseur verra ce champ partiellement écrit. Destructeurs courent même si l'objet a été placé dans un état incohérent par une transaction avortée. Etc. Vous devez être extrêmement défensif lorsque vous écrivez un destructeur.

Cette réponse pourrait également aider:

Quand dois-je créer un destructeur?


7 commentaires

Qu'est-ce que quelque chose n'est pas virtuel a à voir avec cela ne pouvant pas être surchargé? Ou voulez-vous dire de dire remplacer?


Que voulez-vous dire "Les références peuvent être à des choses mortes". Toutes les références d'objet sont garanties de pointer vers des objets .NET valides; Si l'un des objets auquel on a des références directes ou indirectes, le remplacement de la finalisation et n'a pas supprimé la finalisation, il est probable qu'ils ont peut-être exécuté ou être programmé à exécuter, mais aussi longtemps qu'un objet est enregistré pour la finalisation, ni l'un ni l'autre. À laquelle il contient une référence directe ou indirecte peut être retiré de la mémoire. La plus grande difficulté avec la finalisation est probablement le contexte de threading inconnu.


@supercat: bon point. Permettez-moi de clarifier. Supposons que vous ayez deux objets finalisables non analysés qui se réfèrent mutuellement. Le GC les considère que les ordures et les met sur la file d'attente finaliseur, elles sont donc toujours (à peine) vivantes. Une fois le finaliseur du premier exécuté, le deuxième finisseur est un code exécuté dans un objet qui fait référence au premier objet, un objet à peine vivant et a déjà été finalisé. Il semble dangereux pour le deuxième objet de faire une hypothèse sur la première à ce stade.


Un objet ne doit pas faire d'hypothèses sur l'état des objets finalisables dont le code que l'on ne contrôle pas, mais si la classe1 et la classe2 sont à la fois dans le même assemblage, et l'objet1 (de type classe1) contient une référence à l'objet2 (de la classe2) et à l'accès. Dans son finisseur, il doit être préparé pour la possibilité que l'objet2 ait déjà été finalisé, ou peut être une finalisation en attente. Si des références à Object2 ont été exposées à l'extérieur, elle doit également permettre la possibilité que des références en direct externes à Object2 puissent toujours exister. Sous réserve de ces contraintes, cependant, ...


... C'est parfaitement possible et raisonnable pour les objets d'avoir des finaliseurs qui se coordonnent les uns avec les autres. Il a fallu un moment pour que je puisse comprendre comment un cycle de GC divise des objets dans ceux qui ont des racines autres que le mécanisme de finalisation, ceux qui sont enregistrés pour la finalisation, mais n'ont pas Autres racines , et ceux qui n'ont pas de références enracinées. du tout, et à comprendre que la finalisation ne perturbe de quelque manière que ce soit à l'exception d'un objet, sauf pour appeler une finalisation (qui ne perturbera qu'un objet de manière explicitement programmée).


Hey Eric, j'ai marqué votre réponse aussi impolie et si vous le lisez à nouveau, je pense que vous serez d'accord. Peut-être que tu avais une mauvaise journée? :) OP semble venir de C ++ qui rendrait cette question très raisonnable.


@Seth: Je tiens à la réponse. Je n'ai jamais dit que la question était déraisonnable. La réponse est précise et utile.



0
votes

Eh bien, je ne connais pas les destructeurs, mais vous avez d'autres méthodes utiles pour le nettoyage, telles que finaliser () et éliminer () de Idisposable.


3 commentaires

Un destructeur C # et le finaliser sont assez équivalents.


Oui, le destructeur appelle implicitement finaliser (), mais comme il s'agit d'une méthode d'objet, son comportement par rapport à l'héritage / la substitution est beaucoup plus clair.


@Tudor: Malheureusement, les fabricants de C # ont décidé que, étant donné que les gens, si elles sont autorisées à le faire, remplaceraient probablement objet. objet.finalize () . Cela me semble idiot, mais je n'ai pas écrit la langue de la langue.



2
votes

Je fais de la programmation .NET pour près d'une décennie. Le temps seul J'ai mis en œuvre un finisseur, il a fini par être une cause pour une fuite de mémoire et rien d'autre. Vous n'avez presque jamais besoin d'eux.


2 commentaires

Je les ai trouvés très occasionnellement utiles pour les versions de débogage de logiciels. E.g, ~ myClass () {if (this.oops) debug.fail ("oups");}


Ce n'est pas une réponse à la question. Et simplement parce que vous avez peut-être travaillé sur des projets qui n'ont souvent pas que des ressources non gérées ne signifie pas que c'est le cas pour tout le monde utilisant .NET. Je conviendrais que la grande majorité des objets C # n'ont pas besoin de finaliseurs, mais il reste encore de nombreux cas où ils sont nécessaires pour empêcher les fuites de ressources (ou impasse, etc.) lorsque quelque chose d'inattendu se produit empêchant un objet d'être disposé normalement.



1
votes

Une application de console rapide peut aider à tester ce type de chose.

~B
~A


0 commentaires