Commencez par ces classes simples ...
Disons que j'ai un simple ensemble de classes comme celui-ci:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
a un Driver
, le Driver
a deux Shoe
s, chacun Shoe
a un Shoelace
. Tout cela est très idiot.
Ajouter un objet IDisposable à Shoelace
Plus tard, je décide qu'une opération sur le Shoelace
pourrait être multi-thread, donc j'ajoute un EventWaitHandle
pour les threads avec lesquels communiquer. Alors Shoelace
maintenant ressemble à ceci:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Implémenter IDisposable sur Shoelace
Mais maintenant FxCop de Microsoft se plaindra: "Implémentez IDisposable sur 'Shoelace' car il crée des membres des types IDisposable suivants: 'EventWaitHandle'."
D' accord, je mets en œuvre IDisposable
sur Shoelace
et ma petite classe propre cette horrible gâchis devient:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
Ou (comme l'ont souligné les commentateurs) étant donné que Shoelace
lui-même n'a pas de ressources non gérées, je pourrais utiliser l'implémentation plus simple de dispose sans avoir besoin de Dispose(bool)
et Destructor:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
Regardez avec horreur alors que IDisposable se propage
C'est bien ça fixe. Mais maintenant FxCop se plaindra que Shoe
crée un Shoelace
, donc Shoe
doit l'être IDisposable
aussi.
Et Driver
crée Shoe
ainsi Driver
doit être IDisposable
. Et Bus
crée Driver
ainsi Bus
doit être IDisposable
et ainsi de suite.
Soudain, mon petit changement Shoelace
me cause beaucoup de travail et mon patron se demande pourquoi je dois payer Bus
pour faire un changement Shoelace
.
La question
Comment éviter cette propagation IDisposable
, tout en vous assurant que vos objets non gérés sont correctement éliminés?
la source
Réponses:
Vous ne pouvez pas vraiment «empêcher» IDisposable de se propager. Certaines classes doivent être supprimées, par exemple
AutoResetEvent
, et le moyen le plus efficace est de le faire dans laDispose()
méthode pour éviter la surcharge des finaliseurs. Mais cette méthode doit être appelée d'une manière ou d'une autre, donc exactement comme dans votre exemple, les classes qui encapsulent ou contiennent IDisposable doivent les éliminer, elles doivent donc également être jetables, etc. La seule façon de l'éviter est de:using
motif)Dans certains cas, IDisposable peut être ignoré car il prend en charge un cas facultatif. Par exemple, WaitHandle implémente IDisposable pour prendre en charge un Mutex nommé. Si aucun nom n'est utilisé, la méthode Dispose ne fait rien. MemoryStream est un autre exemple, il n'utilise aucune ressource système et son implémentation Dispose ne fait rien non plus. Une réflexion approfondie sur l'utilisation ou non d'une ressource non gérée peut être une instruction. Ainsi peut examiner les sources disponibles pour les bibliothèques .net ou utiliser un décompilateur.
la source
En termes d'exactitude, vous ne pouvez pas empêcher la propagation de IDisposable via une relation d'objet si un objet parent crée et possède essentiellement un objet enfant qui doit maintenant être jetable. FxCop est correct dans cette situation et le parent doit être IDisposable.
Ce que vous pouvez faire, c'est éviter d'ajouter un IDisposable à une classe feuille dans votre hiérarchie d'objets. Ce n'est pas toujours une tâche facile mais c'est un exercice intéressant. D'un point de vue logique, il n'y a aucune raison pour laquelle un ShoeLace doit être jetable. Au lieu d'ajouter un WaitHandle ici, il est également possible d'ajouter une association entre un ShoeLace et un WaitHandle au point où il est utilisé. Le moyen le plus simple consiste à utiliser une instance de Dictionary.
Si vous pouvez déplacer le WaitHandle dans une association lâche via une carte au point où le WaitHandle est réellement utilisé, vous pouvez rompre cette chaîne.
la source
Pour éviter
IDisposable
de se propager, vous devriez essayer d'encapsuler l'utilisation d'un objet jetable à l'intérieur d'une seule méthode. Essayez de concevoirShoelace
différemment:La portée du handle d'attente est limitée à la
Tie
méthode, et la classe n'a pas besoin d'avoir un champ jetable, et n'a donc pas besoin d'être elle-même jetable.Puisque le handle d'attente est un détail d'implémentation à l'intérieur de
Shoelace
, il ne devrait en aucun cas changer son interface publique, comme l'ajout d'une nouvelle interface dans sa déclaration. Que se passera-t-il alors si vous n'avez plus besoin d'un champ jetable, supprimerez-vous laIDisposable
déclaration? Si vous pensez à l'Shoelace
abstraction , vous vous rendez vite compte qu'elle ne devrait pas être polluée par des dépendances d'infrastructure, commeIDisposable
.IDisposable
devrait être réservé aux classes dont l'abstraction encapsule une ressource qui appelle un nettoyage déterministe; c'est-à-dire pour les classes où la disponibilité fait partie de l' abstraction .la source
AutoResetEvent
est utilisé pour communiquer entre différents threads qui opèrent dans la même classe, il doit donc s'agir d'une variable membre. Vous ne pouvez pas limiter sa portée à une méthode. (par exemple, imaginez qu'un thread attend juste du travail en bloquantwaitHandle.WaitOne()
. Le thread principal appelle alors lashoelace.Tie()
méthode, qui fait juste unwaitHandle.Set()
et retourne immédiatement).C'est essentiellement ce qui se passe lorsque vous mélangez la composition ou l'agrégation avec des classes jetables. Comme mentionné, la première solution serait de refactoriser le waitHandle hors du lacet.
Cela dit, vous pouvez réduire considérablement le modèle jetable lorsque vous ne disposez pas de ressources non gérées. (Je cherche toujours une référence officielle pour cela.)
Mais vous pouvez omettre le destructeur et GC.SuppressFinalize (this); et peut-être nettoyer un peu le vide virtuel Dispose (suppression des booléens).
la source
Fait intéressant si
Driver
est défini comme ci-dessus:Ensuite, quand
Shoe
c'est faitIDisposable
, FxCop (v1.36) ne se plaint pas quiDriver
devrait aussi l'êtreIDisposable
.Cependant, s'il est défini comme ceci:
alors il se plaindra.
Je soupçonne que ce n'est qu'une limitation de FxCop, plutôt qu'une solution, car dans la première version, les
Shoe
instances sont toujours créées par leDriver
et doivent toujours être supprimées.la source
Shoe
constructeurs,shoes
serait-elle laissée dans un état difficile?Je ne pense pas qu'il existe un moyen technique d'empêcher IDisposable de se propager si vous gardez votre conception si étroitement couplée. Il faut alors se demander si le design est correct.
Dans votre exemple, je pense qu'il est logique que la chaussure possède le lacet, et peut-être que le conducteur devrait posséder ses chaussures. Cependant, le bus ne devrait pas posséder le conducteur. En règle générale, les chauffeurs de bus ne suivent pas les bus jusqu'à la casse :) Dans le cas des chauffeurs et des chaussures, les chauffeurs fabriquent rarement leurs propres chaussures, ce qui signifie qu'ils ne les «possèdent» pas vraiment.
Une conception alternative pourrait être:
La nouvelle conception est malheureusement plus compliquée, car elle nécessite des classes supplémentaires pour instancier et éliminer des instances concrètes de chaussures et de pilotes, mais cette complication est inhérente au problème en cours de résolution. La bonne chose est que les bus ne doivent plus être jetables simplement pour se débarrasser des lacets.
la source
Que diriez-vous d'utiliser l'inversion de contrôle?
la source