2009-07-07 9 views
19

C# ha l'istruzione using, in particolare per gli oggetti IDisposable. Presumibilmente, qualsiasi oggetto specificato nell'istruzione using conserverà una sorta di risorsa che dovrebbe essere liberata in modo deterministico.Cattive abitudini? L'utilizzo non canonico di C# utilizzando la dichiarazione

Tuttavia, mi sembra che ci siano molti progetti in programmazione che hanno un inizio e una fine singoli, definiti, ma mancano di un supporto linguistico intrinseco. Il costrutto using offre l'opportunità di utilizzare le funzionalità incorporate di un editor di codice per, almeno, evidenziare chiaramente e naturalmente l'ambito di tale progetto o operazione.

Quello che ho in mente è il tipo di operazione che inizia spesso con un metodo BeginXXX() e EndXXX(), anche se ci sono un sacco di sapori diversi, come ad esempio l'esecuzione di codice asincrono che coinvolge una "start" e di una "join".

Prendi questo esempio ingenuo.

webDataOperation.Start(); 
GetContentFromHardDrive(); 
webDataOperation.Join(); 
// Perform operation that requires data from both sources 

Che cosa succede se, invece, il metodo Start restituito un oggetto la cui IDisposable.Dispose metodo esegue l'operazione di join.

using(webDataOperation.Start()) { 
    GetContentFromHardDrive(); 
} 
// Perform operation that requires data from both sources 

O, meglio ancora, quello che ho espressamente avevo in mente: ho un oggetto che fa la grafica altamente specializzati copiarlo sul video e ha un metodo di Begin() e End() (un disegno presente anche in DirectX e XNA). Invece ...

using(blitter.BlitOperation()) { 
    // Do work 
} 
// Use result 

Sembra essere più naturale e leggibile, ma è sconsigliabile, visto che utilizza l'interfaccia IDisposable e la using dichiarazione a fini non intenzionali? In altre parole, questo sarebbe alla pari con o verloading un operatore in modo non intuitivo?

+0

Perché non usare solo {all'inizio e} alla fine? Questo fornisce l'ambito senza abusare dell'uso. – cja

risposta

20

Questa è una pratica perfettamente accettabile. Questi sono chiamati tipi Factored e il Framework Design Guidelines consiglia di fare proprio questo.

In sostanza, se il tipo esegue il wrapping di un'operazione con una durata specifica, utilizzando IDisposable e l'istruzione using diventa una cosa appropriata da considerare.

In realtà ho bloggato circa this specific topic here, pure.

+0

Buona scoperta, ma hai qualche ragionamento per la tua discussione? L'articolo lo consiglia, ma non ne discute perché. – snarf

+0

È perché garantisce che EndOperation() venga sempre chiamato (tramite Dispose()) –

+1

@Snarfblam: in sostanza, l'operazione stessa funge da risorsa che richiede la pulizia. Leggi il mio post sul blog per ulteriori dettagli: è il secondo link. Ci ho messo molto più giustificazione di quanto vorrei qui. –

2

Penso che dovresti usare IDisposable per quello a cui è destinato, e nient'altro. Cioè, se la manutenibilità è importante per te.

+1

John: vedere le linee guida per la progettazione di framework sui tipi di factoring: http://blogs.msdn.com/brada/archive/2009/02/23/framework-design-guidelines-factored-types.aspx - Questo è ora raccomandato approccio alla loro gestione. –

+0

In che modo influisce la manutenibilità? Ed è questa l'unica ragione per cui raccomandi la stretta aderenza al canone? – snarf

+0

Influisce sulla capacità degli altri sviluppatori di capire cosa sta succedendo. La mia ragione per la raccomandazione include il fatto che è abbastanza difficile convincere le persone a usare IDisposable per le normali ragioni: non abbiamo bisogno di altre. –

0

Direi che è accettabile - in effetti, l'ho usato in alcuni progetti in cui volevo che un'azione fosse attivata alla fine di uno specifico blocco di codice.

Wes Deyer utilizzato nel suo LINQ to ASCII programma di arte, lo chiamò azione usa e getta (Wes lavora sul C squadra # compilatore - mi fiderei suo giudizio: D):

http://blogs.msdn.com/wesdyer/archive/2007/02/23/linq-to-ascii-art.aspx

class ActionDisposable: IDisposable 
{ 
    Action action; 

    public ActionDisposable(Action action) 
    { 
     this.action = action; 
    } 

    #region IDisposable Members 

    public void Dispose() 
    { 
     this.action(); 
    } 

    #endregion 
} 

Ora si può tornare che da una funzione, e fare qualcosa di simile:

using(ExtendedConsoleWriter.Indent()) 
{ 
    ExtendedConsoleWriter.Write("This is more indented"); 
} 

ExtendedConsoleWriter.Write("This is less indented"); 
+1

Wow, questo è un palese abuso dell'utilizzo della dichiarazione. Non avrei idea di cosa stesse cercando di fare quel codice senza aprire il codice di ExtendedConsoleWriter. Se ci fossero chiamate a IncreaseIndent e DecreaseIndent attorno al codice, non ci sarebbe alcuna domanda. –

+0

Non userei "using" per questo, ma, supponiamo che ci fosse una parola chiave simile e un'interfaccia associata, come "with": with (writer.Indent()) {stuff; }, sarebbe un costrutto convinent. – snarf

+3

Yikes, Ryan. Era un esempio scritto rapidamente. Sono d'accordo, lo chiamerei IncreaseIndent - nel qual caso non lo chiamerei "un palese abuso dell'utilizzo di dichiarazione". Questo tipo di concetto può essere visto nelle transazioni di scoping su .NET. Sembra che tu stia venendo un po 'bruscamente lì sul minuto. – Charles

5

si tratta di un modello comune, ma personalmente, credo che non ci sono scuse per abusare ID è possibile quando si riesce a ottenere lo stesso effetto in modo molto più ovvio con delegati anonimi e/o lambda; cioè .:

blitter.BlitOperation(delegate 
{ 
    // your code 
}); 
+0

E, se si desidera, diciamo, eseguire sempre il lavoro in un'altra discussione, non è necessario separare il codice (ovvero, questo design aggiunge valore) –

+0

Anche se questo modello mi piace, aggiunge anche questo proprio complessità - in alcuni casi, potrebbe diventare meno ovvio rispetto alla gestione tramite una dichiarazione using. Molti sviluppatori (in particolare per i principianti) hanno problemi con i groove lambda. –

+0

Ciò non consente una sicurezza equivoca, in alcuni casi, anche. Ad esempio, non è possibile utilizzare questo all'interno di un metodo con la dichiarazione di rendimento e ottenere la pulizia garantita ... –

6

Solo perché è possibile (o perché Phil Haack dice che va bene), non significa che si dovrebbe.

La regola pratica di base: se riesco a leggere il codice e capire cosa sta facendo e quale era l'intento, è accettabile. Se, d'altra parte, hai bisogno di spiegare cosa hai fatto, o perché lo hai fatto, probabilmente fara 'saltare gli sviluppatori junior mantenendo il codice.

Ci sono molti altri modelli che possono realizzare questo con una migliore incapsulamento.

La linea di fondo: questa "tecnica" non ti compra nulla e agisce solo per confondere altri sviluppatori.

+0

+1 per il consiglio sulla comprensione. Sono completamente d'accordo, anche se, in generale, penso che l'uso di IDisposable per i tipi factored fornisce un modo molto pulito e comprensibile di gestirli in molti casi ... ma si tratta di scrivere correttamente il codice, oltre ad essere molto ovvio su cosa stai facendo. vale a dire: TransactionScope nel BCL è naturale per me - il codice di rientro in un'altra risposta qui non lo è. Come molte cose, penso che questo sia utile, ma può essere facilmente abusato. –

+0

concordato. Cosa funziona, funziona. È facile lasciarsi trasportare da concetti interessanti, ma nulla che serva solo a confondere dovrebbe essere usato in un progetto del mondo reale. – Charles

+0

Se mi sembrava fonte di confusione, non avrei chiesto. Ignorando la potenziale confusione, hai un'alternativa più ordinata o più semplice? Mi piace il modo in cui "utilizzare" è in linea, rientrato e supportato dalla lingua, ed esplicitamente, concretamente e chiaramente incapsulare l'ambito. – snarf

12

Consiglio contro di esso; la mia convinzione è che il codice sia efficacemente comunicare con il manutentore del codice, non con il compilatore, e dovrebbe essere scritto tenendo presente la comprensione del manutentore. Provo ad usare "usando" solo per disporre di una risorsa, in genere una risorsa non gestita.

Sono in minoranza. La maggior parte delle persone sembra utilizzare "uso" come un fine generale "Voglio un po 'di codice di pulizia per l'esecuzione anche se viene generata un'eccezione" meccanismo.

Non mi piace perché (1) abbiamo già un meccanismo per questo, chiamato "try-finally", (2) utilizza una funzione per uno scopo per cui non è stata progettata e (3) se la chiamata a il codice di pulizia è importante, quindi perché non è visibile nel punto in cui viene chiamato? Se è importante, voglio essere in grado di vederlo.

+0

Mi sembra che questa interpretazione dell '"uso" sia alquanto restrittiva. Per curiosità, c'è qualche altra caratteristica linguistica in C# in cui raccomanderesti contro il suo utilizzo - anche se funziona perfettamente e chiaramente - perché detto uso non era quello per cui è stato progettato? –

+0

@KirkWoll: Sì. Mi raccomando contro l'uso di * tutte * le caratteristiche del linguaggio utilizzate per qualcosa di diverso da quello per cui sono state progettate. –

+0

@EricLippert - ovviamente, non è sempre chiaro per cosa sia stata progettata una funzione linguistica. Infatti, anche nel caso specifico di 'using', ho pensato che ci fosse qualche indicazione che il nome" using "è stato scelto in modo specifico perché era previsto che la funzione _would not_ venisse utilizzata solo per lo smaltimento di risorse non gestite. – kvb

Problemi correlati