2013-08-07 10 views
6

Per motivi di comodità e sicurezza mi piacerebbe utilizzare l'istruzione using per l'allocazione e il rilascio di oggetti da/a un poolUso improprio e Dispose() per la gestione dell'ambito di oggetti non rilasciati?

public class Resource : IDisposable 
{ 
    public void Dispose() 
    { 
     ResourcePool.ReleaseResource(this); 
    } 
} 

public class ResourcePool 
{ 
    static Stack<Resource> pool = new Stack<Resource>(); 

    public static Resource GetResource() 
    { 
     return pool.Pop(); 
    } 

    public static void ReleaseResource(Resource r) 
    { 
     pool.Push(r); 
    } 
} 

e l'accesso alla piscina come

using (Resource r = ResourcePool.GetResource()) 
{ 
    r.DoSomething(); 
} 

ho trovato un po ' argomenti sull'abuso di using e Dispose() per la gestione dell'oscilloscopio, ma tutti loro includono using (Blah b = _NEW_ Blah()).
Qui gli oggetti non devono essere liberati dopo aver lasciato l'ambito di utilizzo ma tenuti nel pool.
Se l'istruzione using si espande semplicemente in un semplice try finally Dispose(), questo dovrebbe funzionare bene ma c'è qualcosa di più che accade dietro le quinte o una possibilità che non funzionerà in futuro.

+0

Chiamare Dispose() è * sempre * opzionale. Cosa succederà quando il programma client non lo chiama? Difficile vedere che non "spingere" qualcosa suona male. Il finalizzatore è il backup di Dispose() non utilizzato, in nessun modo è possibile chiamare ReleaseResource() in un finalizzatore. –

risposta

7

Questo non è un abuso - questo è un idioma di C#. Ad esempio, gli oggetti ADO.NET (collegamenti, le dichiarazioni, i risultati delle query) sono comunemente racchiuse in using blocchi, anche se alcuni di questi oggetti ottenere rilasciato di nuovo alle loro piscine all'interno delle loro Dispose metodi:

using (var conn = new SqlConnection(dbConnectionString)) { 
    // conn is visible inside this scope 
    ... 
} // conn gets released back to its connection pool 
+0

Questo è totalmente offensivo se lo si utilizza intenzionalmente con risorse gestite. – Firoso

2

È un modo valido per utilizzare IDisposable.

In realtà, questo è il modo in pool di connessioni è fatto anche in .NET - avvolgere un oggetto DBConnection in una dichiarazione using per garantire le chiude la connessione e viene restituito al pool di connessioni.

TransactionScope è un altro esempio di una classe che utilizza il modello Dispose per far ritirare le transazioni non-completato:

Una chiamata al metodo Dispose segna la fine del campo di applicazione della transazione.

2

Se il l'uso dell'istruzione si espande semplicemente in un tentativo semplice, infine, Dispose(), questo dovrebbe funzionare bene, ma c'è qualcosa di più che accade dietro le quinte o una possibilità che non funzionerà nelle versioni .Net future?

Lo fa. Il tuo codice dovrebbe funzionare bene ed è garantito dalle specifiche per continuare a funzionare allo stesso modo. In effetti, questo è abbastanza comune (guarda il pooling di connessioni in SQL, per un buon esempio.)

Il problema principale con il tuo codice, come scritto, è che potresti chiamare esplicitamente ReleaseResource all'interno di un using, può causare il pool per ottenere le risorse spinte più di una volta, poiché è una parte dell'API pubblica.

1

vostra comprensione della dichiarazione using è corretta (try, finally, Dispose). Non prevedo che questo cambierà presto. Così tante cose si spezzerebbero se lo facesse.

Non c'è necessariamente qualcosa di sbagliato in quello che stai pianificando. Ho già visto questo genere di cose, in cui Dispose non chiude effettivamente l'oggetto, ma lo inserisce in una sorta di stato che non è "pienamente operativo"."

Se siete a tutti preoccupati per questo, è possibile implementare questo in un modo tale che aderisce al solito schema Dispose attuazione. È sufficiente avere una classe wrapper che implementa IDisposable ed espone tutti i metodi della classe di base. quando l'involucro è disposto, mettere l'oggetto sottostante in piscina, non l'involucro. allora si può considerare l'involucro chiuso, anche se la cosa che non è avvolto.

2

Questo appare come un abuso di IDisposable e una scarsa Per prima cosa costringe gli oggetti che sono stati immagazzinati nel pool a conoscere il pool, è come creare un tipo List che forza gli oggetti in esso contenuti per implementare un'interfaccia particolare o derivare da alcune parti r classe. Come una classe LinkedList che impone agli elementi di dati di includere i puntatori Next e Previous che l'elenco può utilizzare.

Inoltre, il pool assegna la risorsa per l'utente, ma la risorsa ha una chiamata per reinserirsi nel pool. Sembra ... strano.

Penso un'alternativa migliore sarebbe:

var resource = ResourcePool.GetResource(); 
try 
{ 
} 
finally 
{ 
    ResourcePool.FreeResource(resource); 
} 

E 'un po' più di codice (try/finally piuttosto che using), ma un design più pulito. Allevia gli oggetti contenuti dal dover conoscere il contenitore e mostra più chiaramente che il pool sta gestendo l'oggetto.

+0

Sarei d'accordo con te se questo fosse il codice per una biblioteca pubblica. – FrankU

+0

Sarei d'accordo con te se si trattasse di codice per una biblioteca pubblica. Ma poiché questo è per una specifica applicazione/scopo e le risorse reali e le classi di pool sono strettamente collegate, non ritengo che questo sia un problema e come la sintassi di utilizzo breve. – FrankU

0

Quello che stai facendo è come C++ e RAII. E in C#, è all'incirca come quello che si può ottenere con l'idioma C++/RAII.

Eric Lippert, che conosce qualcosa su C#, è categoricamente contrario all'uso di IDispose e all'utilizzo di istruzioni come idioma C# RAII. Vedi la sua risposta in profondità qui, Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?.

Parte del problema con IDisposable utilizzato in questo modo RAII è che IDisposable ha requisiti molto rigorosi da utilizzare correttamente. Quasi tutto il codice C# che ho visto utilizza IDisposable non riesce a implementare correttamente il pattern. Joe Duffy ha creato un post sul blog che fornisce dettagli meticolosi sul modo corretto di implementare il modello IDisposable http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/. Le informazioni di Joe sono molto più dettagliate e complete di quanto menzionato su MSDN. Joe conosce anche qualcosa su C#, e ci sono stati molti contributori molto intelligenti che hanno aiutato a concretizzare quel documento.

Le cose semplici possono essere fatte per implementare il modello minimale IDSposable (per uso come in RAII), ad esempio sigillare la classe, e poiché non ci sono risorse non gestite che non hanno un finalizzatore e così via. MSDN https://msdn.microsoft.com/en-us/library/system.objectdisposedexception%28v=vs.110%29.aspx è una bella panoramica, ma le informazioni di Joe hanno tutti i dettagli cruenti.

Ma l'unica cosa che non si può evitare con IDisposable è la sua natura "virale". Le classi che tengono conto di un membro che è identificabile devono diventare IDisposable ... non un problema nello scenario using(RAII raii = Pool.GetRAII()), ma qualcosa su cui prestare molta attenzione.

Tutto ciò detto, nonostante la posizione di Eric (di cui tendo ad essere d'accordo con lui su quasi tutto il resto), e il saggio di 50 pagine di Joe su come implementare correttamente lo schema IDisposable ... Lo uso come C#/RAII idiom me stesso.

Ora solo se C# aveva 1) i riferimenti non annullabili (come il C++ o D o SpeC#) e 2), i tipi di dati a fondo immutabili (come D, o anche F # [ si può fare il # tipo F di immutabile in C# , ma è un sacco di boilerplate, ed è semplicemente troppo difficile per avere ragione ...rende semplice , e il duro impossibile]) e 3) design per contratto come parte della lingua propriamente detta (come D o Eiffel o SpeC#, non come l'abominio dei codici C#). sigh Forse C# 7.