2009-06-18 10 views
0

J'ai une classe de base qui possède une ressource disponible gérée (.NET PerformanceCounter). Je comprends à propos de l'implémentation d'IDisposable sur la classe afin que je puisse explicitement appeler Dispose sur la ressource. D'après les exemples que j'ai vus, les gens utilisent généralement une variable membre privée "disposée" et la définissent à l'intérieur de Dispose. Plus tard, s'il y a une tentative d'accès à une méthode publique ou une propriété, une exception ObjectDisposedException est levée si "disposé" est vrai.Classe de base IDisposable qui possède une ressource jetable gérée, que faire dans les sous-classes?

Qu'en est-il dans les sous-classes? Comment les sous-classes, dans leurs méthodes et propriétés publiques, sauraient-elles qu'elles ont été éliminées? Au début, je pensais que les sous-classes n'auraient rien de spécial (comme implémenter leur propre version de Dispose) puisque la chose qui doit être éliminée est seulement dans la classe de base (supposons que les sous-classes n'ajouteront pas de données doit être explicitement éliminé) et la classe de base 'Dispose doit gérer cela. Les sous-classes doivent-elles remplacer la méthode virtuelle Dispose de la classe de base uniquement dans le but de définir sa propre variable membre "disposée"?

Voici une version très épurée de la hiérarchie de classes en question.

class BaseCounter : IBaseCounter, IDisposable 
{ 
    protected System.Diagnostics.PerformanceCounter pc; 
    private bool disposed; 
    public BaseCounter(string name) 
    { 
    disposed = false; 
    pc = CreatePerformanceCounter(name); 
    } 

    #region IBaseCounter 
    public string Name 
    { 
    get 
    { 
     if (disposed) throw new ObjectDisposedException("object has been disposed"); 
     return pc.CounterName; 
    } 
    } 
    public string InstanceName 
    { 
    get 
    { 
     if (disposed) throw new ObjectDisposedException("object has been disposed"); 
     return pc.InstanceName; 
    } 
    } 
    #endregion IBaseCounter 

    #region IDisposable 
    protected virtual void Dispose(bool disposing) 
    { 
    if (!disposed) 
    { 
     if (disposing) 
     { 
     if (pc != null) 
     { 
      pc.Dispose(); 
     } 
     pc = null; 
     disposed = true; 
     } 
    } 
    } 

    public void Dispose() 
    { 
    Dispose(true); 
    } 
    #endregion IDisposable 
} 

class ReadableCounter : BaseCounter, IReadableCounter //my own interface 
{ 
    public ReadableCounter(string name) 
    : base(name) 
    { 
    } 

    #region IReadableCounter 
    public Int64 CounterValue() 
    { 
    return pc.RawValue; 
    } 
    #endregion IReadableCounter 
} 

class WritableCounter : BaseCounter, IWritableCounter 
{ 
    public WritableCounter(string name) 
    : base(name) 
    { 
    } 

    #region IWritableCounter 
    public Increment() 
    { 
    pc.Increment(); 
    } 
    #endregion IWritableCounter 
} 

Dans notre système, ReadableCounter et WritableCounter sont les seuls sous-classes de BaseCounter et ils ne sont à un niveau sous-classé plus via un processus de génération de code. Le niveau de sous-classement supplémentaire n'ajoute qu'un nom spécifique de sorte qu'il devient possible de créer des objets correspondant directement aux compteurs nommés (par exemple, si un compteur est utilisé pour compter le nombre de widgets produits, il finit par être encapsulé dans une classe WidgetCounter WidgetCounter contient la connaissance (en fait, juste le nom du compteur sous forme de chaîne) pour permettre la création du compteur de performance "WidgetCounter"

Seules les classes générées par code sont utilisées directement par les développeurs, donc nous aurions quelque chose comme ceci:

class WritableWidgetCounter : WritableCounter 
{ 
    public WritableWidgetCounter 
    : base ("WidgetCounter") 
    { 
    } 
} 

class ReadableWidgetCounter : ReadableCounter 
{ 
    public ReadableWidgetCounter 
    : base ("WidgetCounter") 
    { 
    } 
} 

donc, vous voyez que la classe de base possède et gère l'objet PerformanceCounter (qui est à usage unique), tandis que les sous-classes utilisent le PerformanceCounter.

Si j'ai le code comme ceci:

IWritableCounter wc = new WritableWidgetCounter(); 
wc.Increment(); 
wc.Dispose(); 
wc.Increment(); 
wc = null; 

Comment pourrait-WritableCounter savoir, en Increment, qu'il avait été disposé? Si ReadableCoutner et WritableCounter simplement passer outre

protected virtual void Dispose(bool disposing) 

de quelque chose comme ça du BaseCounter:

protected virtual void Dispose(bool disposing) 
{ 
    disposed = true; //Nothing to dispose, simply remember being disposed 
    base.Dispose(disposing); //delegate to base 
} 

simplement de définir un ReadableCounter/WritableCounter niveau "disposé" variable de membre?

Que diriez-vous si la classe de base (BaseCounter) déclarée disposée comme protégée (ou en a fait une propriété protégée)? De cette façon, les sous-classes pourraient s'y référer plutôt que d'ajouter une méthode Dispose simplement dans le but de se souvenir que Dispose était arrivé.

Ai-je raté le bateau à ce sujet?

Répondre

0

J'ai vu quelques classes jetables avec une propriété publique IsDisposed. Vous pouvez le faire et vérifier dans vos sous-classes.

Une autre chose que j'ai faite est une méthode 'Validate' générique protégée que toutes les méthodes de sous-classes appellent (et pourraient remplacer). Si cela revient, tout va bien, sinon il pourrait lancer. Cela isolerait complètement vos sous-classes des entrailles jetables.

+1

Je pense que cette réponse ainsi que la prochaine réponse d'Alan McBee sont valides. Pour ma situation, je pense que cette réponse est plus proche de donner le comportement que je veux (la classe de base est jetable, les sous-classes sont aussi jetables, mais n'ont pas vraiment leurs propres ressources "jetables"). J'ai trouvé une approche similaire ici: http://gregbeech.com/blogs/tech/archive/2007/03/07/implementing-and-using-the-idisposable-interface.aspx – wageoghe

0

J'ai des extraits que j'utilise pour implémenter IDisposable, à la fois dans la classe de base et dans les sous-classes. Vous voudriez probablement celui pour la sous-classe.

J'ai balayé la majeure partie de ce code de MSDN, je pense.

Voici le code de la classe de base IDisposable (pas celui que vous voulez):

#region IDisposable Members 
// Track whether Dispose has been called. 
private bool _disposed = false; 

// Implement IDisposable. 
// Do not make this method virtual. 
// A derived class should not be able to override this method. 
public void Dispose() 
{ 
    Dispose(true); 
    // Take yourself off the Finalization queue 
    // to prevent finalization code for this object 
    // from executing a second time. 
    GC.SuppressFinalize(this); 
} 

// Dispose(bool disposing) executes in two distinct scenarios. 
// If disposing equals true, the method has been called directly 
// or indirectly by a user's code. Managed and unmanaged resources 
// can be disposed. 
// If disposing equals false, the method has been called by the 
// runtime from inside the finalizer and you should not reference 
// other objects. Only unmanaged resources can be disposed. 
protected virtual void Dispose(bool disposing) 
{ 
    // Check to see if Dispose has already been called. 
    if (!this._disposed) 
    { 
     // If disposing equals true, dispose all managed 
     // and unmanaged resources. 
     if (disposing) 
     { 
      // TODO: Dispose managed resources. 

     } 
     // Release unmanaged resources. If disposing is false, 
     // only the following code is executed. 
     // TODO: Release unmanaged resources 

     // Note that this is not thread safe. 
     // Another thread could start disposing the object 
     // after the managed resources are disposed, 
     // but before the disposed flag is set to true. 
     // If thread safety is necessary, it must be 
     // implemented by the client. 
    } 
    _disposed = true; 
} 

// Use C# destructor syntax for finalization code. 
// This destructor will run only if the Dispose method 
// does not get called. 
// It gives your base class the opportunity to finalize. 
// Do not provide destructors in types derived from this class. 
~Program() 
{ 
    // Do not re-create Dispose clean-up code here. 
    // Calling Dispose(false) is optimal in terms of 
    // readability and maintainability. 
    Dispose(false); 
} 
#endregion 

Et voici le code que je l'utilise dans les sous-classes (ce qui est le code que vous voulez):

#region IDisposable Members 
// Track whether Dispose has been called. 
private bool _disposed = false; 

// Design pattern for a derived class. 
// Note that this derived class inherently implements the 
// IDisposable interface because it is implemented in the base class. 
// This derived class does not have a Finalize method 
// or a Dispose method without parameters because it inherits 
// them from the base class. 
protected override void Dispose(bool disposing) 
{ 
    if (!this.disposed) 
    { 
     try 
     { 
      if (disposing) 
      { 
       // Release the managed resources you added in 
       // this derived class here. 
       // TODO: Dispose managed resources. 
      } 
      // Release the native unmanaged resources you added 
      // in this derived class here. 
      // TODO: Release unmanaged resources. 
      _disposed = true; 
     } 
     finally 
     { 
      // Call Dispose on your base class. 
      base.Dispose(disposing); 
     } 
    } 
} 
#endregion 

Recherchez les marques TODO:.