Utiliser les références faibles sous .NETDate de publication : 17/04/2006 , Date de mise a jour : 17/04/2006 Dans un environnement managé comme .NET la gestion
des références à des instances de classe semble naturelle
et sans poser de souci. Un objet est soit référencé,
donc « vivant », soit n’est plus référencé,
donc « mort » et éligible à sa destruction par
le Garbage Collector (plus loin noté GC). I. Définition II. Les mécanismes en jeu III. L'intérêt IV. Mise en oeuvre IV.a. Version C# IV.b. Version Delphi.NET V. Conclusion I. Définition Une référence faible est une référence
à un objet qui bien qu’elle en autorise l’accès
n’interdit pas la possible suppression de ce dernier par le Garbage
Collector.
En clair, cela signifie qu’une référence faible,
d’où sa « faiblesse », ne créé
pas un lien fort avec l’instance référencée.
C’est une référence qui, du point de vue du système
de libération des instances n’existe pas, elle n’est
pas prise en compte dans le graphe des chemins des objets « accessibles
» par le GC. C’est en fait comme cela que tout fonctionne en POO classique
dans des environnements non managés comme Win32, rien n’interdit
une variable de pointer un objet qui a déjà été
libéré, mais tout accès par ce biais se soldera
par une violation d’accès. En effet, sous .NET une telle situation ne peut tout simplement pas
arriver puisqu’on ne libère pas la mémoire explicitement,
c’est le CLR qui s’en charge lorsqu’il n’y a
plus de référence à l’objet. II. Les mécanismes en jeu Le Garbarge Collector du CLR libère la mémoire
de tout objet qui ne peut plus être atteint. Un objet ne peut plus
être atteint quand toutes les références qui le pointent
deviennent non valides, par exemple en les forçant à null
(nil sous Delphi). Lorsqu’il
détruit les objets qui se trouvent dans cette situation le GC appelle
leur méthode Finalize,
à condition qu’une telle méthode soit définie
et que le GC en ait été informé (le mécanisme
réel est plus complexe et sort du cadre de cet article).
Lorsqu’un objet peut être directement ou indirectement atteint il ne peut pas être supprimé par le GC. Une référence vers un objet qui peut être atteint est appelée une référence forte. Une référence faible permet elle aussi de pointer un
objet qui peut être atteint qu’on appelle la cible (target
en anglais). Mais cette référence n’interfère
pas avec le GC qui, si aucune référence forte n’existe
sur l’objet, peut détruire ce dernier en ignorant les éventuelles
références faibles (elles ne sont pas totalement ignorées
puisque, nous allons le voir, la référence faible sera
avertie de la destruction de l’objet). Pour un système de cache, comme évoqué en introduction,
cela est très intéressant puisqu’on peut libérer
des objets (plus aucune référence valide ne le pointe)
et malgré tout le récupérer dans de nombreux cas
si le besoin s’en fait sentir. Cela est possible car entre le
moment où un objet devient éligible pour sa destruction
par le GC et le moment où il est réellement collecté
et finalisé il peut se passer un temps non négligeable
! III. L'intérêt Les références faibles ne servent
pas qu’à mettre en œuvre des systèmes de cache,
elles servent aussi lorsqu’on doit pointer des objets qui peuvent
et doivent éventuellement être détruits. Rappelons-nous
: si nous utilisons une simple référence sur un tel objet,
il ne sera jamais détruit puisque justement nous le référençons…
Les références faibles permettent d’échapper
à ce mécanisme par défaut qui, parfois, devient une
gêne plus qu’un avantage.
Un exemple d’une telle situation : Supposons une liste de personnes. Cette liste pointe donc des instances de la classe Personne. Imaginons maintenant que l’application autorise la création de « groupes de travail », c'est-à-dire des listes de personnes. Si les listes définissant les groupes de travail pointent directement les instances de Personne et si une personne est supprimée de cette liste, les groupes de travail continuerons de « voir » cette personne puisque l’instance étant référencée (référence forte) elle ne sera pas détruite par sa simple suppression de la liste des personnes… En fait, on souhaitera dans un tel cas que toute personne supprimée de la liste principale n’apparaisse plus dans les groupes de travail dans lesquels elle a pu être référencée. Cela peut se régler par une gestion d’événement : toute suppression de la liste des personnes entraînera le balayage de tous les groupes de travail pour supprimer la personne. Cette solution n’est pas toujours utilisable. Les références faibles deviennent alors une alternative intéressante, notamment parce qu’il n’y a pas besoin d’avoir prévu un lien entre la liste principale et les listes secondaires qui peuvent être ajoutées après coup dans la conception de l’application et parce que la suppression d’une personne n’impose pas une attente en raison du balayage de toutes les listes secondaires. IV. Mise en oeuvreIl est temps de voir comment implémenter les références
faibles. La classe WeakReference
Un mot sur cette dernière propriété : Lorsque
l’on créé une référence faible on
peut indiquer dans le constructeur, en plus de l’objet ciblé,
un paramètre booléen qui fixera la valeur de TrackResurrection.
Lorsque la valeur est « false
» (par défaut) on parle de référence faible
« courte », lorsque la valeur est « true
» on parle de référence faible « longue ».
Le code qui suit est auto-documenté par sa simple exécution. S’agissant de projets console il vous suffit de créer une nouvelle application de ce type (que ce soit sous C# ou Delphi.Net) et de faire un copier / coller du code proposé. Lancez l’exécution (F5 sous VS2003/2005, F9 sous BDS) et laissez-vous guider à l’exécution en jetant un œil sur le code... IV.a Version C#Voici la version C#. Elle est développée en framework 1.1 afin que tous les lecteurs puissent la tester (le même code fonctionne en framework 2.0). De même, nous avons choisi le mode "application console" pour éviter de s'encombrer avec la mise en page.
using System;
using System.Collections;
namespace od.article.WR
{
class Class
{
public class Personne
{
private string _Nom;
public string Nom
{
get { return _Nom; }
set { _Nom = (value!=null) ? value.Trim() : string.Empty; }
}
public Personne(string nom)
{
this._Nom = nom.Trim();
}
}
public static ArrayList Employés;
private static string _line = "-------------------";
private static void Return()
{
Console.WriteLine("<return> pour continuer...");
Console.ReadLine();
}
public static void ListeEmployés()
{
Console.WriteLine(_line);
foreach ( Personne p in Employés)
Console.WriteLine(p.Nom);
Console.WriteLine(_line);
Return();
}
[STAThread]
static void Main(string[] args)
{
Employés = new ArrayList();
Employés.Add(new Personne("Olivier"));
Employés.Add(new Personne("Barabara"));
Employés.Add(new Personne("Jacky"));
Employés.Add(new Personne("Valérie"));
Console.WriteLine("Liste originale");
ListeEmployés();
Personne p = (Personne)Employés[0]; // pointe "olivier"
Employés.RemoveAt(0); // suppresson de "olivier" dans la liste
Console.WriteLine("p pointe : " + p.Nom);
Console.WriteLine("L'élément 0 de la liste a été supprimé");
Console.WriteLine();
ListeEmployés();
Console.WriteLine("Mais l'objet pointé par p existe toujours : "+p.Nom);
Return();
WeakReference wr = new WeakReference(Employés[0]); // pointe "barbara"
Console.WriteLine(
"wr est une référence faible sur: "+((Personne)wr.Target).Nom);
Return();
Console.WriteLine(
"La cible de wr est vivante ? : "+wr.IsAlive.ToString());
Return();
Employés.RemoveAt(0); // suppression de "barbara"
Console.WriteLine(
"L'élément 0 ('barbara') a été supprimé. La liste devient :");
ListeEmployés();
Console.WriteLine(
"La cible de wr est vivante ? : "+wr.IsAlive.ToString());
Console.WriteLine(
"On peut réacquérir la cible : "+((Personne)wr.Target).Nom );
Return();
Console.WriteLine("Mais si le GC passe par là...");
GC.Collect(GC.MaxGeneration);
Console.WriteLine(
"La cible de wr est vivante ? : "+wr.IsAlive.ToString()); // false !
Console.WriteLine(
"La référence faible n'a pas interdit sa destruction totale.");
Return();
}
}
}
IV.b. Version Delphi.NET Voici la version Delphi.NET. Elle est développée
en framework 1.1 (par force puisque Highlander n'est pas encore sorti).
De même, nous avons choisi le mode "application console"
pour éviter de s'encombrer avec la mise en page.
program od.article.wr;
{$APPTYPE CONSOLE}
uses
SysUtils,
StrUtils,
System.Collections;
type
Personne = class
private
_Nom : string;
public
procedure setNom(const Value: string);
property Nom:string read _Nom write setNom;
constructor Create(nom:string);
end;
{ Personne }
procedure Personne.setNom(const Value: string);
begin
_nom := IfThen(Value<>nil,Value.Trim(),System.&String.Empty);
end;
constructor Personne.Create(nom: string);
begin
inherited Create();
_nom := nom.Trim();
end;
{ fin Personne }
var Employés : ArrayList;
const _line = '----------------';
procedure Return();
begin
Console.WriteLine('<return> pour continuer...');
Console.ReadLine();
end;
procedure ListeEmployés();
var p:Personne;
begin
Console.WriteLine(_line);
for p in Employés do Console.WriteLine(p.Nom);
Console.WriteLine(_line);
Console.WriteLine();
Return();
end;
var p:Personne;
wr:WeakReference;
begin
Employés := ArrayList.Create();
Employés.Add(Personne.Create('Olivier'));
Employés.Add(Personne.Create('Barbara'));
Employés.Add(Personne.Create('Jacky'));
Employés.Add(Personne.Create('Valérie'));
Console.WriteLine('Liste originale');
ListeEmployés();
p := Personne(Employés[0]); // pointe "olivier"
Employés.RemoveAt(0); // suppression "olivier" dans liste
Console.WriteLine('p pointe : '+p.Nom);
Console.WriteLine('l''élement 0 de la liste a été supprimé');
Console.WriteLine();
ListeEmployés();
Console.WriteLine('Mais l''objet pointé par p existe toujours : '+p.nom);
Return();
wr := WeakReference.Create(Employés[0]); // pointe "barbara"
Console.WriteLine('wr est une référence faible sur : '+Personne(wr.Target).Nom);
Return();
Console.WriteLine('La cible de wr est vivante ? : '+wr.IsAlive.ToString());
Employés.RemoveAt(0); // suppression de la liste de "barbara"
Return();
Console.WriteLine('l''élément 0 (barbara) a été supprimé, la liste devient : ');
ListeEmployés();
Console.WriteLine('La cible de wr est vivante ? : '+wr.IsAlive.ToString());
Console.WriteLine('On peut réacquérir la cible : '+Personne(wr.Target).Nom);
Return();
Console.WriteLine('Mais si le GC passe par là...');
GC.Collect(GC.MaxGeneration);
Console.WriteLine('La cible de wr est vivante ? : '+wr.IsAlive.ToString());
Console.WriteLine('La référence faible n''a pas interdit la suppression de la cible.');
Return();
end.
V. ConclusionCet article est un peu "austère", pas de jolis diagrammes UML qui font branché, pas de capture d'écran pour enjoliver, du texte et du code... L'auteur a bien conscience que ce côté aride aura peut-être rebuté quelques lecteurs, alors, à vous qui en êtes arrivé à cette conclusion il tient à vous dire merci... et ... Bon développement !
|
||||||||
Copyright © 2006 O.DAHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Cette page est déposée.
Copyright © 2000-2012 - www.developpez.com