C#: détruire un objet

Voici l’exemple suivante.

La class AppSession contient utilise une objet “session” qui permet de se connecter à une application quelconque. Dans le constructeur on se connecte et dans le destructeur on se déconnecte.

[code]public class AppSession
{
private Asession session;

public AppSession(string login,string password)
{
session = new Asession;
session.login(login,password);
}

~Appsession()
{
session.logout();
}
}[/code]

Or lorsqu’on fait le code suivant:

[code]public class frmMain
{
private AppSession session;

private void btnExtract_Click(object sender, System.EventArgs e)
{
session = new AppSession(login,password);
//do stuff
session = null;
}
}[/code]
L’objet AppSession n’est détruit qu’une foi la fenêtre frmMain fermée et non lorsque toutes le code pour le clic d’un bouton (btnExtract_Click) est effectué.
Ma question est donc: comment détruire pour de bon objet (et donc appeler son destructeur) car apparemment ce n’est pas “objet = null;”.

Ce n’est pas à toi de le faire, c’est le Garbage Collector (i.e la machine viruelle) qui s’en charge.
Le C# n’est pas le C++.

Il te faut le gérer “dans le code”, à l’aide de IDisposable (par exemple). Là tu pourras avoir explicitement la main sur le cycle de vie de l’objet par du code tel que using(AppSession session = new AppSession(…)) { }

Ton code devient :

[codebox]public class frmMain
{
private void btnExtract_Click(object sender, System.EventArgs e)
{
using (AppSession session = new AppSession(login,password))
{
//do stuff
}
}
}[/codebox]

Si tu as besoin de cet objet dans plusieurs méthodes et qu’il te faut donc stocker l’objet dans un champ, il te faut faire différement :

  • Si tu utilises le designer de formulaires de visual studio, il surcharge déjà la méthode Dispose(), donc tu dois surcharger l’évènement OnClose et appeller Dispose(), ou implémenter IContainer (si c’est approprié) et ajouter ton objet au champ components géré par le designer.

  • Si tu utilises une classe quelconque, alors elle doit elle aussi implémenter IDisposable etc.

Un bon lien pour bien piger l’implémentation du pattern IDisposable : http://msdn.microsoft.com/en-us/library/ms244737(VS.80).aspx

Note : coder du comportement lourd (donc non lié uniquement à la libération des ressources) dans un finalizer est une très mauvaise chose.

Pour écrire du code de libération déterministe, on implémente IDisposable. L’implémentation qu’on en fait est généralement la suivante :

[code]public class AppSession : IDisposable
{
public AppSession(string login,string password)
{
session = new Asession;
session.login(login,password);
}

// Le code de nettoyage peut être surchargé dans une classe dérivée
protected virtual void Dispose(bool disposing)
{
// disposing est à true en cas de destruction déterministe, et à false si appelé par le garbage collector
session.logout();
}

// Définit dans IDisposable
public void Dispose()
{
Dispose(true); // Appel dispose de manière déterministe
GC.SuppressFinalize(this); // le code de libération a été fait de manière déterministe,
// on indique donc au garbage collector de ne pas appeler le finaliseur
}

// Finaliseur (on ne parle pas de destructeur en .Net, car c’est une notion déterministe, mais de finaliseur)
~AppSession()
{
Dispose(false); // appel dispose de manière non-déterministe (par le GC)
}
}[/code]

pour controler de manière déterministe l’exécution du code de nettoyage, on utilisera le mot clef using (qui ne fonctionne qu’avec des objets IDisposable):

public void Main() { using(AppSession appSession = new AppSession(...)) { // utilisation de la session } // en sortant du scope, on appelle obligatoirement la méthode Dispose() (de manière déterministe) // même en cas d'exception (sauf ThreadAbortException il me semble), même en cas de return }

Voili voilou pour le nettoyage de resources déterministe en .Net

Ok, merci girafologue pour ce cours.

Mais maintenant j’ai une clarification afin d’éclairer ma culture personnelle: lorsqu’on laisse le garbage collector se charger de tout, comment ça se passe pour les 2 exemples ci-dessous?

public class frmMain { private void btnExtract_Click(object sender, System.EventArgs e) { string strTest = "hello word!"; } }
strTest est détruit que lorsque la méthode btnExtract_Click est terminée?

public class frmMain { private string strTest; private void btnExtract_Click(object sender, System.EventArgs e) { strTest = "hello word!"; } }
strTest est détruit que lorsque frmMain est détruit … enfin finalisé :slight_smile: ?

En fait on ne sait jamais avec précision. Simplement, par comptage de références et détection des ilots de référneces cycliques, le Garbage Collector maintient une liste des objets qu’il doit détruire. La destruction en elle-même a lieu… un jour ! En gros, quand le Garbage Collector estime qu’il gènera le moins possible l’exécution du programme (en gros, quand il se sent obligé de demander de la mémoire au système, quand il trouve que ca fait bien longtemps que le CPU a rien foutu, quand il trouve que la conso mémoire du process abuse un peu…).

Ca a 2 avantages :

[ul]
[li]Ca diminue de beaucoup les “chances” de laisser le développeur faire des memory leaks (je te rassure quand même c’est toujours possible, mais ca relève plus de l’erreur de conception que de l’erreur technique)[/li][li]De temps en temps, ca peut être bénéfique lors de la création de beaucoup d’objets utilisés un tres court moment par exemple (ca évite de faire beaucoup de désallocations de mémoire, et donc d’appels système pendant le traitement).[/li][/ul]En plus, les petits gars de MSR qui ont développé le GC de la CLR .Net sont pas trop manchot en fait, donc c’est une implémentation, qui dans le patoi du geek moyen peut être qualifié de “qui poutraille sa maman” (ca a très longtemps été un très gros argument en faveur de .Net par rapport à Java, qui a eu bien du mal à rattraper son retard).

/me s’incline

J’aurais pas pu dire mieux girafologue, tres bonnes explications.

Je rajouterais quand meme qu’il est rarement necessaire d’implementer IDisposable, a moins d’avoir des resources qui sont IDisposable dans ta classe ou des resources qui ne sont pas managées.

D’ailleurs, comment ca se passe dans un bout, de code unsafe avec des pointeurs, c’est toujours géré par le GC ?

:slight_smile: Alors là j’aimerais beaucoup que tu m’en dises un peu plus à ce sujet. En 5 ans de taf (C# et Java), j’ai toujours entendu/lu l’inverse concernant le GC.

[quote=« Gynsu2000, post:1, topic: 48079 »]Voici l’exemple suivante.
…[/quote]
Oui, super mauvais pattern en C#. A ne pas faire, et comme dit plus haut, il faut pas implementer IDisposable a moins d’avoir d’autres object IDisposable ou des ressources natives qui appartiennent a la classe en question par interop. Ca semble pas etre le cas ici. Une chose est sure, faire des trucs dans le finaliseur: tres mauvaise idee.

Twin: on se demande ou t’as pu lire ca, y a clairement pas photo, et a part dans des cas pathologiques de benchmarks fait pour mettre en evidence un comportement particulier le GC de .Net est (etait?) clairement superieur aux implementation de Java (et vu qu’il y en a plusieur, ca depend comment). Suffit de faire du code de cochon sur les deux pour s’en rendre compte :slight_smile: (en particulier si tu fais un jeu a 60fps) .Net a une seule implementation mais plusieurs modes de fonctionnement, un mode « client » et un mode « serveur », et une implementation differente pour l’embarqué et .Net CF.

Ben comme je ne sais pas trop où non plus, je vais vous faire confiance. Je n’ai jamais cherché à pousser le GC dans ses derniers retranchement non plus, ni fait d’étude particulière sur le sujet.

Mon domaine est malheureusement assez loin des jeux, du coup les contraintes ne sont pas les mêmes.

Un truc plus proche de ce que je fais serait

[code]public class AppSession
{
private Asession session;

~Appsession()
{
if (session!=null)
{
session.logout();
session = null;
}
}

public void Login(string login,string password)
{
session = new Asession;
session.login(login,password);
}

public void Logout();
{
session.logout();
session = null;
}
}[/code]

L’idée c’est de forcer le logout de l’objet session si quelqu’un « oublie » de faire un AppSession.Logout() afin de libérer des ressources le cas échéant.
Et justement cette classe session est un objet issue d’une dll COM (qui passe par une dll interop).

Tu es donc pile dans le cas où implémenter IDisposable a du sens : tu dois pouvoir contrôler la portée du truc -> le bloc using(){} est là pour ca, et si tu utilises le morceau de code que j’ai mis plus haut pour faire ton implémentation, tu es sûr qu’en cas d’oublie, ton objet COM sera quand même libéré.

Au passage, en période de debugging, faire un “System.Diagnostics.Debug.Assert(disposing);” au début de la méthode Dispose(bool disposing), c’est plutôt une bonne idée : Ca te permet de détecter les oublis. Attention à le mettre en compilation conditionnelle par contre.

L’implémentation que j’ai faite plus haut est en fait tirée de ce que génère le compilateur C++ CLI quand on implémente un destructeur. (la notion de destructeur en C++ est déterministe, et le compilateur génère donc une implémentation de IDisposable pour la reflèter). C’est assez troublant d’ailleurs quand on ne connait pas bien C++ CLI : ~MaClasse() génère un destructeur (une méthode Dispose en fait), et !MaClasse() génère la partie “custom” du code du finaliseur.

Tu peux mettre les Debug.Assert sans compilation conditionelle, ils sont pas mis dans le code release. J’ai jamais vu mettre de #if DEBUG autour en tout cas :).

Au temps pour moi, il me semblait que les appels de méthode restaient, mais simplement que le comportement en mode Release était différent.

La plupart des methodes de la classe Debug ont deja un attribut System.Diagnostics.ConditionalAttribute(« DEBUG ») mis dessus, donc les appels a ces fonctions sont virés a la compilation.
Aie confiance, Microsoft s’occupe de tout pour toi :slight_smile:

[quote=“lordabdul, post:15, topic: 48079”]La plupart des methodes de la classe Debug ont deja un attribut System.Diagnostics.ConditionalAttribute(“DEBUG”) mis dessus, donc les appels a ces fonctions sont virés a la compilation.
Aie confiance, Microsoft s’occupe de tout pour toi :)[/quote]

Arf… Comment il réponds super vite a une question que j’ai même pas posée …
(comment le compilo il gère tout ca … bref vive les Attributes)

[quote=“Tzim, post:16, topic: 48079”]Arf… Comment il réponds super vite a une question que j’ai même pas posée …
(comment le compilo il gère tout ca … bref vive les Attributes)[/quote]
He he, ouais les attributs ca taraude du phallus d’iguane a 360 degres.

[quote=« Gynsu2000, post:1, topic: 48079 »]~Appsession()
{
session.logout();
}
}[/quote]

Ouh ca pique les yeux :slight_smile:

Tout à déjà été dit au dessus … mais quand même … ca pique les yeux )

Je confirme, les attributes, ca tue (les yeux)

[DisplayName("Teinte")] [DisplayNameEx("X", "R")] [PropertyLook("X", typeof(PropertySliderLook))] [PropertyFeel("X", CustomSmartPropertyGrid.FeelSlider)] [PropertyValidator("X", typeof(PropertyValidatorSlider), 0f, 5f)] [TrackBarSettings("X", 0.01f, 0.01f, 0.1f)] [UpDownSettings("X", 0.01f)] [DisplayNameEx("Y", "G")] [PropertyLook("Y", typeof(PropertySliderLook))] [PropertyFeel("Y", CustomSmartPropertyGrid.FeelSlider)] [PropertyValidator("Y", typeof(PropertyValidatorSlider), 0f, 5f)] [TrackBarSettings("Y", 0.01f, 0.01f, 0.1f)] [UpDownSettings("Y", 0.01f)] [DisplayNameEx("Z", "B")] [PropertyLook("Z", typeof(PropertySliderLook))] [PropertyFeel("Z", CustomSmartPropertyGrid.FeelSlider)] [PropertyValidator("Z", typeof(PropertyValidatorSlider), 0f, 5f)] [TrackBarSettings("Z", 0.01f, 0.01f, 0.1f)] [UpDownSettings("Z", 0.01f)] [DisplayNameEx("W", "A")] [PropertyLook("W", typeof(PropertySliderLook))] [PropertyFeel("W", CustomSmartPropertyGrid.FeelSlider)] [PropertyValidator("W", typeof(PropertyValidatorSlider), 0f, 5f)] [TrackBarSettings("W", 0.01f, 0.01f, 0.1f)] [UpDownSettings("W", 0.01f)] public Vector4 DefaultTint { get { return m_defaultTint; } set { m_defaultTint = value; PropChanged("DefaultTint"); } }

WTF… ? :slight_smile: