Novembre 2016

Volume 31 Numero 11

Il presente articolo è stato tradotto automaticamente.

.NET Framework - Tipi Disposable nascosti

Da Artak Mkrtchyan | Novembre 2016

Tipi Disposable sono un'ottima idea, poiché consentono di liberare risorse in modo deterministico. Tuttavia, esistono situazioni in cui gli sviluppatori lavorano con i tipi disposable senza rendersene conto. L'utilizzo di modelli di progettazione creational è riportato un esempio di una situazione in cui l'utilizzo di un tipo disposable potrebbe non essere evidente, che può determinare un oggetto non ottenere eliminato. Questo articolo verranno illustrati i modi per gestire il problema. Inizierò esaminando alcuni dei modelli di progettazione creational.

Modelli di progettazione creational

Un grande vantaggio dei modelli di progettazione creational è astratta lontano le effettive implementazioni che "parlare" con la lingua dell'interfaccia. Essi riguardano i meccanismi di creazione oggetto per creare oggetti che sono adatti alla soluzione. In confronto alla creazione dell'oggetto di base, modelli di progettazione creational migliorano diversi aspetti del processo di creazione oggetto. Di seguito sono due vantaggi ben noti dei modelli di progettazione creational:

  • Astrazione: È il tipo di oggetto viene creato, il chiamante non sapete cosa effettivo oggetto restituito è astratto, sono consapevole solo l'interfaccia.
  • Elementi interni di creazione: Incapsulano le informazioni sulla creazione dell'istanza di tipo specifico.

Successivamente, fornirò una breve panoramica dei due modelli di progettazione creational noto.

Il modello di progettazione metodo Factorymodello di progettazione il metodo Factory è uno dei miei Preferiti e si utilizza molto nel mio lavoro quotidiano. Questo modello utilizza metodi factory per affrontare il problema di creazione degli oggetti senza specificare la classe esatta dell'oggetto creato. Anziché chiamare direttamente un costruttore di classe, si chiama un metodo factory per creare l'oggetto. Il metodo factory restituisce un'astrazione (interfaccia o una classe di base), classi implementano l'interfaccia figlio. Figura 1 viene illustrato il diagramma di modellazione UML (Unified Language) per questo modello.

In Figura 1, il ConcreteProduct è un tipo specifico dell'interfaccia di astrazione IProduct /. Analogamente, il ConcreteCreator è un'implementazione specifica dell'interfaccia ICreator.

Modello di progettazione dei metodi factory
Figura 1 Modello di progettazione dei metodi Factory

Il client di questo modello utilizza un'istanza di ICreator e verrà utilizzato il metodo Create per ottenere una nuova istanza della IProduct, senza sapere quale prodotto effettivo è stato restituito.

Il modello Factory astratta è l'obiettivo del modello di progettazione di Factory astratta per fornire un'interfaccia per la creazione di gruppi di oggetti correlati o dipendenti senza specificare implementazioni concrete.

Questo shelters il codice client la necessità di creazione di un oggetto con il client di richiedere l'oggetto factory per creare un oggetto del tipo astratto desiderato e restituire un puntatore astratto dell'oggetto al client. In particolare, questo significa che il codice client dispone di informazioni sul tipo concreto. Riguarda solo con un tipo astratto.

Aggiunta del supporto per nuovi tipi concreti viene gestita la creazione di nuovi tipi di factory e modificando il codice client per utilizzare un tipo di factory diversi in base alle esigenze. Nella maggior parte dei casi, si tratta di una modifica del codice una riga. Ciò ovviamente semplifica la gestione delle modifiche, come il codice del client non è necessario per il nuovo tipo di factory. Figura 2 viene illustrato il diagramma UML per il modello di progettazione di Factory astratta.

Modello di struttura astratta Factory
Figura 2 astratta Factory Design Pattern

Dalla prospettiva del client, l'utilizzo della Factory astratta viene rappresentata con il seguente frammento di codice:

IAbstractFactory factory = new ConcreteFactory1();
IProductA product = factory.CreateProductA();

Il client è possibile modificare l'implementazione della factory effettivo per controllare il tipo di prodotto creato dietro le quinte e che non avranno assolutamente alcun impatto sul codice.

Questo codice è solo un esempio; nel codice strutturato correttamente, la creazione di istanze di factory stesso sarebbe probabilmente possibile speditamente, con il modello di un metodo factory come esempio.

Il problema

In entrambi gli esempi di modello di progettazione, si è verificato una factory coinvolti. Una factory è l'effettiva/routine del metodo, che restituisce un riferimento al tipo costruito tramite un'astrazione in risposta alla chiamata del client.

Tecnicamente, è possibile utilizzare una factory per creare un oggetto, se esiste un'astrazione, come illustrato nella Figura 3.

Esempio di astrazione e sul relativo utilizzo
Figura 3 esempio di astrazione e sul relativo utilizzo

La factory gestisce la scelta tra diverse implementazioni disponibili, in base ai fattori coinvolti.

Secondo il principio di inversione delle dipendenze:

  • Moduli di alto livello non devono dipendere da moduli di livello inferiore. Entrambi invece devono dipendere da astrazioni.
  • Le astrazioni non devono dipendere dettagli. Dettagli devono dipendere da astrazioni.

Questa operazione, tecnicamente, significa che in ogni livello di una catena di dipendenze, la dipendenza deve essere sostituita da un'astrazione. Inoltre, la creazione di queste astrazioni può, in molti casi dovrebbe essere, gestita tramite le factory.

Tutto ciò sottolinea l'importanza factory sono nelle routine di codifica. Tuttavia, sono in effetti nascosta il problema: i tipi disposable. Prima di approfondire i dettagli, innanzitutto parlerò l'interfaccia IDisposable e il modello Dispose.

Modello di progettazione Dispose

Tutti i programmi acquisiscono risorse quali memoria, handle di file e le connessioni al database durante la loro esecuzione. Gli sviluppatori devono prestare attenzione quando si utilizzano tali risorse, poiché le risorse devono essere rilasciate dopo che è stato acquisiti e utilizzati.

Common Language Runtime (CLR) fornisce supporto per la gestione automatica della memoria tramite il garbage collector (GC). Non è necessario liberare spazio nella memoria gestita in modo esplicito perché il catalogo globale che eseguirà automaticamente. Sfortunatamente, esistono altri tipi di risorse (detta risorse non gestite) che devono ancora essere rilasciate esplicitamente. Il GC non è progettato per gestire questi tipi di risorse, pertanto è responsabilità dello sviluppatore di rilasciare quelle.

Tuttavia, CLR consente agli sviluppatori di affrontare le risorse non gestite. Il tipo System. Object definisce un metodo virtuale pubblico, denominato Finalize, viene chiamato dal GC prima che venga recuperata la memoria dell'oggetto. Il metodo Finalize in genere è detto finalizzatore. È possibile sostituire il metodo per pulire altre risorse non gestite utilizzate dall'oggetto.

Questo meccanismo, tuttavia, presenta alcuni inconvenienti legati a causa di alcuni aspetti dell'esecuzione del catalogo globale.

Il finalizzatore viene richiamato quando il Garbage Collector rileva che un oggetto è idoneo per la raccolta. Ciò accade in un determinato periodo dopo l'oggetto non è più necessaria.

Quando il Garbage Collector deve chiamare il finalizzatore, dovrà essere rimandare la raccolta di memoria effettiva per il successivo ciclo di garbage collection. Questo viene posticipato raccolta della memoria dell'oggetto più lungo. Che è dove entra in gioco l'interfaccia IDisposable. Microsoft .NET Framework fornisce l'interfaccia IDisposable che è necessario implementare per fornire allo sviluppatore un meccanismo per il rilascio delle risorse non gestite manualmente. Tipi che implementano questa interfaccia sono denominati tipi disposable. L'interfaccia IDisposable definisce solo un metodo senza parametri denominato Dispose. Dispose deve essere chiamato per rilasciare subito le risorse non gestite che fa riferimento, non appena l'oggetto non è necessaria.

Si potrebbe chiedere, "Perché è necessario chiamare Dispose manualmente quando si ritiene che GC verrà infine gestirla per me?" La risposta richiede un articolo distinto, che interessa anche gli aspetti dell'impatto di esecuzione del GC sulle prestazioni. Questo esula dall'ambito di questo articolo, quindi si passerà.

Esistono alcune regole da seguire quando si decide se un tipo deve essere disposable. La regola generale è la seguente: Se un oggetto di un determinato tipo sta per fare riferimento a una risorsa non gestita o altri oggetti disposable, deve anche essere disposable.

Il modello Dispose definisce un'implementazione specifica per l'interfaccia IDisposable. Richiede due metodi Dispose l'implementazione: un pubblico senza parametri (definiti dall'interfaccia IDisposable) e l'altro un protetto virtuale con un singolo parametro booleano. Ovviamente, se il tipo deve essere bloccato, il metodo protetto virtuale devono essere sostituiti da privati.

Figura 4 implementazione del modello di progettazione di Dispose

public class DisposableType : IDisposable {
  ~DisposableType() {
    this.Dispose(false);
  }
  public void Dispose() {
    this.Dispose(true);
    GC.SuppressFinalize(this);
  }
  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      // Dispose of all the managed resources here
    }
    // Dispose of all the unmanaged resources here
  }
}

Il parametro booleano indica il modo in cui la chiamata del metodo dispose. Il metodo pubblico chiama quella protetto con un parametro di valore "true". Analogamente, gli overload del metodo Dispose (bool) nella gerarchia di classi devono chiamare base. Dispose (true).

L'implementazione del modello Dispose richiede inoltre che il metodo Finalize sottoposto a overload. Questa operazione viene eseguita per coprire scenari in cui uno sviluppatore si dimentica di chiamare il metodo Dispose dopo che l'oggetto non è più necessaria. Perché il finalizzatore viene chiamato dal GC, le risorse gestite riferimento potrebbe essere già (o sarà) pulire, pertanto è necessario gestire il rilascio delle risorse non gestite solo quando viene chiamato il metodo Dispose (bool) dal finalizzatore.

Passare all'argomento principale, il problema si verifica quando si gestiscono oggetti disposable quando utilizzata con i modelli di progettazione creational.

Si immagini uno scenario in cui uno dei tipi di concreto che implementa l'astrazione anche implementa l'interfaccia IDisposable. Si supponga che sia ConcreteImplementation2 nel mio esempio, come illustrato nella Figura 5.

Astrazione con un'implementazione di IDisposable
Figura 5 astrazione con un'implementazione di IDisposable

Si noti che l'interfaccia IAbstraction non eredita dall'interfaccia IDisposable.

Esaminare il codice client, in cui l'astrazione sta per essere utilizzato. Come l'interfaccia IAbstraction non è stato modificato, il client non interessa anche le modifiche potenziali dietro le quinte. Naturalmente, il client non indovinare che egli ha un oggetto, che oggi è responsabile per l'eliminazione. La realtà è che un'istanza di IDisposable effettivamente non è previsto presente e in molti casi, tali oggetti non vengono eliminati in modo esplicito dal codice client.

La speranza è che l'effettiva implementazione di ConcreateImplementation2 implementa il Pattern di progettazione Dispose, che non è sempre il caso.

Ora è ovvio che il meccanismo più semplice di gestire il caso in cui l'istanza restituita IAbstraction implementa anche l'interfaccia IDisposable, è possibile introdurre un controllo esplicito nel codice client, come illustrato di seguito:

IAbstraction abstraction = factory.Create();
try {
  // Operations with abstraction go here
}
finally {
  if (abstraction is IDisposable)
    (abstraction as IDisposable).Dispose();
}

Questa operazione, tuttavia, non appena diventa una procedura noiosa.

Sfortunatamente, un utilizzo blocco non può essere utilizzato con IAbstraciton, come, non estendere IDisposable in modo esplicito. In modo inventati con una classe helper, che include la logica nel blocco finally e consente di utilizzare il comando nonché bloccare. Figura 6 viene illustrato il codice completo della classe e fornisce inoltre un esempio di utilizzo.

Figura 6 PotentialDisposable tipo e il relativo utilizzo

public sealed class PotentialDisposable<T> : IDisposable where T : class {
  private readonly T instance;
  public T Instance { get { return this.instance; } }
  public PotentialDisposable(T instance) {
    if (instance == null) {
      throw new ArgumentNullException("instance");
    }
    this.instance = instance;
  }
  public void Dispose() {
    IDisposable disposableInstance = this.Instance as IDisposable;
    if (disposableInstance != null) {
      disposableInstance.Dispose();
    }
  }
}
The client code:
IAbstraction abstraction = factory.Create();
using (PotentialDisposable<IAbstraction> wrappedInstance =
  new PotentialDisposable<IAbstraction>(abstraction)) {
    // Operations with abstraction wrapedInstance.Instance go here
}

Come può vedere nella parte "Il codice client" di Figura 6, utilizzando la classe PotentialDisposable < T > ridotto il codice del client a solo un paio di righe con un blocco.

Si potrebbe affermare che è possibile solo aggiornare l'interfaccia IAbstraction e renderlo IDisposable. Questo potrebbe essere la soluzione preferita in alcune situazioni, ma non altri.

In una situazione in cui si è proprietari dell'interfaccia IAbstraction ed è opportuno per IAbstraction estendere IDisposable, è consigliabile farlo. In realtà, un buon esempio di questo sarebbe classe System.IO.Stream astratta. La classe implementa effettivamente l'interfaccia IDisposable, ma non dispone di alcuna logica effettiva definito. Il motivo è che il writer della classe sapevamo che la maggior parte delle classi figlie avrà un tipo di membri disposable.

Un'altra situazione: Quando si è proprietari dell'interfaccia IAbstraction, ma non ha senso per poter estendere IDisposable, come la maggior parte delle implementazioni di esso non sono eliminabili. Si pensi a un'interfaccia ICustomCollection come esempio. Si dispongono di diverse implementazioni in memoria e improvvisamente è necessario aggiungere un'implementazione supportate da database, che sarà l'implementazione solo disposable.

La situazione finale sarebbe quando non si dispone di interfaccia IAbstraction, in modo non si dispone di controllo su di essa. Si consideri un esempio di ICollection, supportata da un database.

Conclusioni

Se è l'astrazione che si ottiene tramite un metodo factory, è importante da tenere presenti disposables durante la scrittura di codice del client. Utilizzo di questa classe helper semplice è un modo per verificare che il codice è più efficiente possibile quando si gestiscono oggetti disposable.


Artak Mkrtchyan è un software engineer senior che vivono a Redmond, Washington.  Egli amano codifica quanto ha amano pesca.

Grazie al seguente esperto tecnico Microsoft per la revisione dell'articolo: Paul Brambilla
Paul Brambilla è uno sviluppatore software senior presso Microsoft ed è specializzato nei servizi cloud e infrastruttura di base.