2009-05-17 6 views
1

Je ne me souviens jamais de toutes les règles d'implémentation de l'interface IDisposable, j'ai donc essayé de créer une classe de base qui s'occupe de tout cela et qui rend l'IDisposable facile à implémenter. Je voulais juste entendre votre opinion si cette implémentation est correcte telle quelle ou si vous voyez quelque chose que je pourrais améliorer. L'utilisateur de cette classe de base est supposé en dériver, puis implémenter les deux méthodes abstraites ReleaseManagedResources() et ReleaseUnmanagedResources(). Donc, voici le code:Cette implémentation IDisposable est-elle correcte?

public abstract class Disposable : IDisposable 
{ 
    private bool _isDisposed; 
    private readonly object _disposeLock = new object(); 

    /// <summary> 
    /// called by users of this class to free managed and unmanaged resources 
    /// </summary> 
    public void Dispose() { 
     DisposeManagedAndUnmanagedResources(); 
    } 

    /// <summary> 
    /// finalizer is called by garbage collector to free unmanaged resources 
    /// </summary> 
    ~Disposable() { //finalizer of derived class will automatically call it's base finalizer 
     DisposeUnmanagedResources(); 
    } 

    private void DisposeManagedAndUnmanagedResources() { 
     lock (_disposeLock) //make thread-safe 
      if (!_isDisposed) { //make sure only called once 
       try { //suppress exceptions 
        ReleaseManagedResources(); 
        ReleaseUnmanagedResources(); 
       } 
       finally { 
        GC.SuppressFinalize(this); //remove from finalization queue since cleaup already done, so it's not necessary the garbage collector to call Finalize() anymore 
        _isDisposed = true; 
       } 
      } 
    } 

    private void DisposeUnmanagedResources() { 
     lock (_disposeLock) //make thread-safe since at least the finalizer runs in a different thread 
      if (!_isDisposed) { //make sure only called once 
       try { //suppress exceptions 
        ReleaseUnmanagedResources(); 
       } 
       finally { 
        _isDisposed = true; 
       } 
      } 
    } 

    protected abstract void ReleaseManagedResources(); 
    protected abstract void ReleaseUnmanagedResources(); 

} 

Répondre

2

Vous accédez à l'objet géré _disposeLock dans le finaliseur. Il a peut-être déjà été collecté par les ordures d'ici là. Vous ne savez pas quelles seraient les implications de cela car vous ne l'utilisez que pour verrouiller.

La sécurité du fil semble être excessive. Je ne pense pas que vous ayez à vous soucier des conflits entre le thread GC et votre thread d'application.

Sinon, cela semble correct.

+0

Merci Joe, oui je vais prendre la serrure dans le finaliseur. –

+0

Non, il ne peut pas avoir été collecté, car le jetable actuel contient une référence à celui-ci. –

+2

"Non, cela n'a pas pu être collecté" - c'est faux. Il peut avoir été collecté - à partir de MSDN: "Les finaliseurs de deux objets ne sont pas forcément exécutés dans un ordre spécifique, même si un objet se réfère à l'autre, si l'objet A a une référence à l'objet B , L'objet B peut déjà avoir été finalisé lorsque le finaliseur de l'objet A démarre. "(Voir http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx) – Joe

2

EDIT Oui, j'ai mal lu la partie de séparer et les ressources gérées disposant non gérés (toujours sur la première tasse de café).

Cependant, le verrouillage n'est presque certainement pas nécessaire. Oui, lors d'une mise au rebut passive, le code sera exécuté sur le thread finalizer qui est différent du thread sur lequel il était initialement exécuté. Cependant, si l'objet est en train d'être finalisé de cette manière, le CLR a déjà déterminé qu'il n'existe aucune référence à cet objet et donc l'a collecté. Donc, il n'y a pas d'autre endroit qui peut appeler votre disposer à ce moment-là et donc il n'y a pas de raison pour un verrou.

Un couple d'autres commentaires de style. Pourquoi les méthodes sont-elles abstraites?

Pourquoi? Ce faisant, vous forcez les classes dérivées à implémenter des méthodes pour disposer des ressources gérées et non gérées, même si elles n'en disposent pas. Il est vrai qu'il n'y a aucun intérêt à dériver de cette classe si vous n'avez rien à éliminer. Mais je pense que c'est assez commun d'avoir seulement l'un ou l'autre mais pas les deux. Je les rendrais virtuels ou abstraits.

Vous évitez également d'éliminer deux fois mais vous ne faites rien pour avertir le développeur qu'il dispose d'un double objet. Je me rends compte que la documentation MSDN dit que la double disposition devrait être essentiellement une interdiction, mais en même temps dans quelles circonstances cela devrait-il être légal? En général, c'est une mauvaise idée d'accéder à un objet après qu'il a été éliminé. Une double disposition nécessite la réutilisation d'un objet disposé et est probablement un bug (ceci peut arriver si la finalisation n'est pas supprimée dans une disposition active mais c'est aussi une mauvaise idée). Si je mettais en œuvre ceci je jetterais sur double disposition pour avertir le développeur qu'ils utilisaient un objet incorrectement (c'est-à-dire l'utiliser après qu'il était déjà disposé).

+0

WTF? Avez-vous lu mon code? Mon code fait exactement ce que vous décrivez.Aussi je dois verrouiller le code dipose car au moins le finaliseur fonctionne dans un thread différent de mon application! –

+2

@Hermann, oui j'ai mal lu cette partie. Le WTF est un peu inutile cependant. – JaredPar

+0

@JaredPar, désolé pour le WTF, mais j'ai eu le sentiment que vous n'avez pas lu mon code du tout. Merci de signaler que le verrouillage du finaliseur n'est pas nécessaire. Je vais quand même le conserver dans la méthode Dispose car ça ne fait pas de mal quand il n'est pas utilisé et c'est bien dans les rares cas où Dispose est en fait appelé par un utilisateur sur différents threads (ce qui est possible après tout). En ce qui concerne la double disposition, MSDN dit: "une méthode Dispose doit être appelable plusieurs fois sans émettre d'exception." –

12

Je ne peux pas vraiment commenter l'exactitude de votre code, mais je me demande si vous trouverez que la classe jetable à la base de votre arbre d'héritage est aussi flexible qu'elle doit l'être. Ce ne sera pas très utile lorsque vous voulez hériter de quelque chose que vous ne possédez pas. Je pense qu'IDispoasble est laissé en tant qu'interface en raison des nombreux domaines différents dans lesquels il pourrait être utilisé. Fournir une implémentation concrète au pied de la hiérarchie de l'héritage serait ... limitatif.

+0

Bon point. Dériver de Jetable ne permet pas de dériver de n'importe quelle autre classe et qui rend inutile le jetable dans de nombreux cas. – Vadim

+0

Eh bien, bien sûr, mais je voudrais simplement copier le code de cette classe de base. Et dans ces cas, je possède le code, je peux juste en dériver. –

+0

@Vadim: Dans vb.net au moins, il peut être utile d'avoir du code qui a accès aux paramètres du constructeur avant les initialiseurs de champs. La seule façon de le faire est de faire appel au constructeur de la classe dérivée pour appeler un constructeur de classe de base avec ces paramètres, et de demander au constructeur de la classe de base de copier ces paramètres dans les champs de la classe dérivée. Si l'on fait cela, on peut tout aussi bien jeter la logique de l'élimination. – supercat

0

Si vous êtes préoccupé par la sécurité de fil puis j'utiliser les opérations emboîtés légers plutôt qu'un verrou lourd:

class MyClass: IDispose { 
    int disposed = 0; 

    public void Dispose() 
    { 
    Dispose(true); 
    GC.SuppressFinalize(this); 
    } 

    public virtual void Dispose(bool disposing) 
    { 
    if (0 == Thread.InterlockedCompareExchange(ref disposed, 1, 0)) 
    { 
     if (disposing) 
     { 
      // release managed resources here 
     } 
     // release unmanaged resources here 
    } 
    } 

    ~MyClass() 
    { 
    Dispose(false); 
    } 
} 
+0

A la réflexion, le thread qui échoue à la fonction changeexchange doit encore attendre celui qui a réussi, sinon il peut supprimer la mémoire de sous sous la branche live if, donc cela nécessiterait une syncronisation (par exemple ManualResetEvent), ce qui le rendrait tout aussi lourd comme une serrure. –

Questions connexes