È abusivo usare IDisposable e "usare" come mezzo per ottenere un "comportamento con ambito" per la sicurezza delle eccezioni?


112

Qualcosa che ho usato spesso in C ++ era lasciare che una classe Agestisse una condizione di entrata e uscita di stato per un'altra classe B, tramite il Acostruttore e il distruttore, per assicurarmi che se qualcosa in quell'ambito avesse generato un'eccezione, allora B avrebbe uno stato noto quando il l'ambito è stato chiuso. Questo non è puro RAII per quanto riguarda l'acronimo, ma è comunque un modello consolidato.

In C #, spesso voglio farlo

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

Che deve essere fatto in questo modo

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

se voglio garantire lo Frobblestato al FiddleTheFrobbleritorno. Il codice sarebbe più carino con

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

dove FrobbleJanitorsembra all'incirca piace

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

Ed è così che voglio farlo. Ora la realtà raggiunge, poiché ciò che voglio usare richiede che FrobbleJanitorvenga usato con using . Potrei considerarlo un problema di revisione del codice, ma qualcosa mi sta tormentando.

Domanda: quanto sopra sarebbe considerato un uso abusivo di usinge IDisposable?


23
+1 solo per i nomi delle classi e dei metodi :-). Beh, per essere onesti, e la domanda vera.
Joey

2
@Johannes: Sì, posso capire perché - la maggior parte delle persone si limita a suonare il violino, ma devo solo protestare contro questo. ;-) E, a proposito, +1 per la somiglianza del tuo nome.
Johann Gerell

Forse puoi usare l'ambito lock (this) {..} per ottenere lo stesso risultato in questo esempio?
dal

1
@ oɔɯǝɹ: usi lock () per impedire l'accesso simultaneo a qualcosa da thread diversi. Niente a che vedere con lo scenario che descrivo.
Johann Gerell

1
IScopeable senza Finalizer nella prossima versione di C # :)
Jon Raynor

Risposte:


33

Non credo sia così, necessariamente. IDisposable tecnicamente è pensato per essere utilizzato per cose che hanno risorse non gestite, ma la direttiva using è solo un modo accurato di implementare un modello comune di try .. finally { dispose }.

Un purista direbbe "sì, è offensivo", e nel senso purista lo è; ma la maggior parte di noi non codifica da una prospettiva purista, ma da una semi-artistica. Usare il costrutto "using" in questo modo è davvero artistico, secondo me.

Probabilmente dovresti attaccare un'altra interfaccia sopra IDisposable per spingerla un po 'più lontano, spiegando agli altri sviluppatori perché quell'interfaccia implica IDisposable.

Ci sono molte altre alternative per farlo ma, alla fine, non riesco a pensare a nessuna che sia così bella, quindi provaci!


2
Credo che sia anche un uso artistico dell '"utilizzo". Penso che il post di Samual Jack che collega a un commento di Eric Gunnerson convalidi questa implementazione di "using". Posso vedere ottimi punti fatti sia da Andras che da Eric su questo.
IAbstract

1
Ne ho parlato con Eric Gunnerson tempo fa e, secondo lui, era intenzione del team di progettazione C # che l'uso denotasse gli ambiti. In effetti, ha postulato che se avessero progettato l'uso prima dell'istruzione lock, potrebbe non esserci nemmeno un'istruzione lock. Sarebbe stato un blocco utilizzando con una chiamata Monitor o qualcosa del genere. AGGIORNAMENTO: Ho appena realizzato che la prossima risposta è di Eric Lippert, che fa anche parte del team linguistico. ;) Forse il team di C # stesso non è pienamente d'accordo su questo? Il contesto della mia discussione con Gunnerson era la classe TimedLock
Haacked

2
@Haacked - Divertente non è vero; il tuo commento conferma totalmente il mio punto di vista; che avevi parlato con un ragazzo della squadra e lui era d'accordo; sottolineando poi Eric Lippert di seguito, della stessa squadra non è d'accordo. Dal mio punto di vista; C # fornisce una parola chiave che sfrutta un'interfaccia e produce anche un modello di codice dall'aspetto piacevole che funziona in così tanti scenari. Se non dovremmo abusarne, allora C # dovrebbe trovare un altro modo per applicarlo. Ad ogni modo, i designer probabilmente dovranno mettere le loro anatre in fila qui! Nel frattempo: using(Html.BeginForm())signore? non importa se lo faccio signore! :)
Andras Zoltan

71

Considero questo un abuso della dichiarazione using. Sono consapevole di essere in minoranza in questa posizione.

Considero questo un abuso per tre ragioni.

Primo, perché mi aspetto che "using" sia utilizzato per utilizzare una risorsa e smaltirla quando hai finito . Cambiare lo stato del programma non sta usando una risorsa e cambiarlo non significa eliminare nulla. Pertanto, "usare" per mutare e ripristinare lo stato è un abuso; il codice è fuorviante per il lettore occasionale.

Secondo, perché mi aspetto che "usare" sia usato per cortesia, non per necessità . Il motivo per cui usi "using" per eliminare un file quando hai finito non è perché è necessario farlo, ma perché è educato - qualcun altro potrebbe essere in attesa di usare quel file, quindi dicendo "fatto adesso "è la cosa moralmente corretta da fare. Mi aspetto che dovrei essere in grado di effettuare il refactoring di un "utilizzo" in modo che la risorsa utilizzata venga trattenuta più a lungo e smaltita in un secondo momento, e che l'unico impatto di ciò sia di disturbare leggermente altri processi . Un blocco "using" che ha un impatto semantico sullo stato del programma è offensivo perché nasconde un'importante, richiesta mutazione dello stato del programma in un costrutto che sembra essere lì per comodità e cortesia, non per necessità.

E terzo, le azioni del tuo programma sono determinate dal suo stato; la necessità di un'attenta manipolazione dello stato è precisamente il motivo per cui stiamo avendo questa conversazione in primo luogo. Consideriamo come potremmo analizzare il tuo programma originale.

Se dovessi portarlo a una revisione del codice nel mio ufficio, la prima domanda che ti farei è "è davvero corretto bloccare il frobble se viene generata un'eccezione?" È palesemente ovvio dal tuo programma che questa cosa blocca nuovamente il frobble in modo aggressivo, indipendentemente da ciò che accade. È giusto? È stata generata un'eccezione. Il programma è in uno stato sconosciuto. Non sappiamo se Foo, Fiddle o Bar hanno lanciato, perché hanno lanciato o quali mutazioni hanno eseguito in altri stati che non sono stati ripuliti. Puoi convincermi che in quella terribile situazione è sempre la cosa giusta da fare richiudere?

Forse lo è, forse non lo è. Il punto è che con il codice come è stato scritto originariamente, il revisore del codice sa di porre la domanda . Con il codice che usa "using", non so fare la domanda; Presumo che il blocco "using" allochi una risorsa, la usi per un po 'e la elimini educatamente quando ha finito, non che la parentesi graffa di chiusura del blocco "using" muti lo stato del mio programma in una circostanza eccezionale quando arbitrariamente molti le condizioni di coerenza dello stato del programma sono state violate.

L'uso del blocco "using" per avere un effetto semantico rende questo programma frammentato:

}

estremamente significativo. Quando guardo quel tutore singolo vicino non penso immediatamente "quel tutore ha effetti collaterali che hanno impatti di vasta portata sullo stato globale del mio programma". Ma quando si abusa dell '"uso" in questo modo, improvvisamente lo fa.

La seconda cosa che ti chiederei se vedessi il tuo codice originale è "cosa succede se viene lanciata un'eccezione dopo lo sblocco ma prima che venga inserita la prova?" Se stai eseguendo un assembly non ottimizzato, il compilatore potrebbe aver inserito un'istruzione no-op prima del tentativo ed è possibile che si verifichi un'eccezione di interruzione del thread sul no-op. Questo è raro, ma accade nella vita reale, in particolare sui server web. In tal caso, lo sblocco avviene ma il blocco non avviene mai, perché l'eccezione è stata lanciata prima del tentativo. È del tutto possibile che questo codice sia vulnerabile a questo problema e debba essere effettivamente scritto

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

Di nuovo, forse sì, forse no, ma so di porre la domanda . Con la versione "using", è suscettibile allo stesso problema: un'eccezione di interruzione del thread potrebbe essere generata dopo che Frobble è stato bloccato ma prima che venga inserita la regione protetta da try associata all'uso. Ma con la versione "using", presumo che questo sia un "e allora?" situazione. È un peccato se ciò accade, ma presumo che l '"utilizzo" sia lì solo per essere educato, non per mutare lo stato del programma di vitale importanza. Presumo che se qualche terribile eccezione di interruzione del thread si verifica esattamente nel momento sbagliato, allora, vabbè, il garbage collector pulirà quella risorsa alla fine eseguendo il finalizzatore.


18
Anche se risponderei alla domanda in modo diverso, penso che sia un post ben argomentato. Tuttavia, sono in conflitto con la tua affermazione che usingè un problema di cortesia piuttosto che di correttezza. Credo che le perdite di risorse siano problemi di correttezza, sebbene spesso siano abbastanza minori da non essere bug ad alta priorità. Pertanto, i usingblocchi hanno già un impatto semantico sullo stato del programma e ciò che impediscono non è solo un "inconveniente" per altri processi, ma forse un ambiente danneggiato. Questo non vuol dire che anche il diverso tipo di impatto semantico implicito nella domanda sia appropriato.
kvb

13
Le perdite di risorse sono problemi di correttezza, sì. Ma il mancato utilizzo di un "utilizzo" non perde la risorsa; la risorsa verrà eventualmente ripulita quando viene eseguito il finalizzatore. La pulizia potrebbe non essere così aggressiva e tempestiva come vorresti, ma accadrà.
Eric Lippert

7
Post meraviglioso, ecco i miei due centesimi :) Sospetto che molto "abuso" usingsia perché è un tessitore orientato all'aspetto di un uomo povero in C #. Ciò che lo sviluppatore vuole veramente è un modo per garantire che un codice comune venga eseguito alla fine di un blocco senza dover duplicare quel codice. C # non supporta AOP oggi (MarshalByRefObject e PostSharp non resistono). Tuttavia, la trasformazione dell'istruzione using da parte del compilatore rende possibile ottenere una semplice AOP riproponendo la semantica dell'eliminazione deterministica delle risorse. Anche se non è una buona pratica, a volte è interessante
lasciar

11
Anche se generalmente sono d'accordo con la tua posizione, ci sono alcuni casi in cui il vantaggio di riproporre usingè troppo allettante per rinunciare. Il miglior esempio a cui riesco a pensare è monitorare e segnalare i tempi del codice: using( TimingTrace.Start("MyMethod") ) { /* code */ } ancora una volta, questo è AOP: Start()cattura l'ora di inizio prima dell'inizio del blocco, Dispose()cattura l'ora di fine e registra l'attività. Non cambia lo stato del programma ed è indipendente dalle eccezioni. È anche attraente perché evita un po 'di tubature e il suo uso frequente come modello mitiga la semantica confusa.
LBushkin

6
Buffo, ho sempre pensato che l'utilizzo fosse un mezzo per supportare la RAII. Mi sembra ragionevolmente intuitivo considerare un lucchetto un tipo di risorsa.
Brian

27

Se vuoi solo del codice pulito e con scope, potresti anche usare lambdas, á la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

dove il .SafeExecute(Action fribbleAction)metodo avvolge il blocco try- catch- finally.


In quell'esempio, ti sei perso lo stato di ingresso. Inoltre, assicurati di concludere prova / finalmente, ma non chiami nulla alla fine, quindi se qualcosa nel lambda lancia non hai idea in quale stato ti trovi.
Johann Gerell,

1
Ah! Ok, immagino tu voglia dire che SafeExecuteè un Fribblemetodo che chiama Unlock()e Lock()nel wrapping prova / finalmente. Allora le mie scuse. Ho subito visto SafeExecutecome un metodo di estensione generico e quindi ho menzionato la mancanza di stato di ingresso e uscita. Colpa mia.
Johann Gerell

1
Stai molto attento con questo apporach. Potrebbero esserci alcune pericolose estensioni di vita non intenzionali nel caso di gente del posto catturata!
jason

4
Che schiffo. Perché nascondere try / catch / infine? Nasconde il codice da leggere per gli altri.
Frank Schwieterman

2
@ Yukky Frank: Non era mia idea "nascondere" nulla, era la richiesta dell'interrogante. :-P ... Detto questo, la richiesta è finalmente una questione di "Don't Repeat Yourself". Potresti avere molti metodi che richiedono lo stesso "frame" boilerplate per acquisire / rilasciare qualcosa in modo pulito e non vuoi imporlo al chiamante (pensa all'incapsulamento). Inoltre si potrebbero nominare metodi come .SafeExecute (...) in modo più chiaro per comunicare abbastanza bene quello che fanno.
herzmeister

25

Eric Gunnerson, che faceva parte del team di progettazione del linguaggio C #, ha dato questa risposta più o meno alla stessa domanda:

Doug chiede:

re: un'istruzione di blocco con timeout ...

Ho già fatto questo trucco per gestire schemi comuni in numerosi metodi. Di solito l' acquisizione di blocchi , ma ce ne sono altri . Il problema è che sembra sempre un hack poiché l'oggetto non è realmente usa e getta quanto " call-back-at-the-end-of-a-scope-grado ".

Doug,

Quando abbiamo deciso [sic] l'istruzione using, abbiamo deciso di chiamarla "using" piuttosto che qualcosa di più specifico per la disposizione degli oggetti in modo che potesse essere utilizzata esattamente per questo scenario.


4
Ebbene, quella citazione dovrebbe dire a cosa si riferiva quando ha detto "esattamente questo scenario", poiché dubito che si riferisse a questa domanda futura.
Frank Schwieterman

4
@ Frank Schwieterman: ho completato la citazione. A quanto pare, la gente del # squadra C ha fatto pensare la usingfunzione è stata non a limitarsi a disposizione delle risorse.
paercebal

11

È un pendio scivoloso. IDisposable ha un contratto, supportato da un finalizzatore. Un finalizzatore è inutile nel tuo caso. Non puoi costringere il cliente a usare l'affermazione using, incoraggialo solo a farlo. Puoi forzarlo con un metodo come questo:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

Ma questo tende a diventare un po 'imbarazzante senza i lama. Detto questo, ho usato IDisposable come hai fatto tu.

C'è tuttavia un dettaglio nel tuo post che lo rende pericolosamente vicino a un anti-pattern. Hai detto che questi metodi possono generare un'eccezione. Questo non è qualcosa che il chiamante può ignorare. Può fare tre cose al riguardo:

  • Non fare nulla, l'eccezione non è recuperabile. Il caso normale. Chiamare Unlock non ha importanza.
  • Cattura e gestisci l'eccezione
  • Ripristina lo stato nel suo codice e lascia che l'eccezione superi la catena di chiamate.

Gli ultimi due richiedono che il chiamante scriva esplicitamente un blocco try. Ora l'istruzione using si intromette. Potrebbe indurre un cliente in coma che gli fa credere che la tua classe si sta occupando dello stato e non è necessario svolgere alcun lavoro aggiuntivo. Non è quasi mai accurato.


Il caso forzato è stato presentato da "herzmeister der welten" sopra, credo, anche se l'esempio dell'OP non sembrava gradire.
Benjamin Podszun

Sì, ho interpretato il suo SafeExecutecome un metodo di estensione generico. Vedo ora che probabilmente era inteso come un Fribblemetodo che chiama Unlock()e Lock()nel pacchetto try / infine. Colpa mia.
Johann Gerell,

Ignorare un'eccezione non significa che non sia recuperabile. Significa che verrà gestito sopra nello stack. Ad esempio, le eccezioni possono essere utilizzate per uscire correttamente da un thread. In questo caso, devi liberare correttamente le tue risorse, inclusi i lucchetti bloccati.
paercebal

7

Un esempio del mondo reale è BeginForm di ASP.net MVC. Fondamentalmente puoi scrivere:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

o

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

Html.EndForm chiama Dispose e Dispose restituisce solo il </form>tag. La cosa buona di questo è che le parentesi {} creano uno "scope" visibile che rende più facile vedere cosa c'è nel form e cosa no.

Non lo abuserei, ma essenzialmente IDisposable è solo un modo per dire "DEVI chiamare questa funzione quando hai finito". MvcForm lo usa per assicurarsi che il form sia chiuso, Stream lo usa per assicurarsi che lo stream sia chiuso, puoi usarlo per assicurarti che l'oggetto sia sbloccato.

Personalmente lo userei solo se le seguenti due regole sono vere, ma sono stabilite arbitrariamente da me:

  • Dispose dovrebbe essere una funzione che deve sempre essere eseguita, quindi non dovrebbero esserci condizioni eccetto Null-Check
  • Dopo Dispose (), l'oggetto non dovrebbe essere riutilizzabile. Se volessi un oggetto riutilizzabile, preferirei dargli metodi di apertura / chiusura piuttosto che smaltirlo. Quindi lancio un'eccezione InvalidOperationException quando provo a utilizzare un oggetto disposto.

Alla fine, è tutta una questione di aspettative. Se un oggetto implementa IDisposable, presumo che debba fare un po 'di pulizia, quindi lo chiamo. Penso che di solito sia meglio avere una funzione "Shutdown".

Detto questo, non mi piace questa frase:

this.Frobble.Fiddle();

Dato che il FrobbleJanitor "possiede" il Frobble ora, mi chiedo se non sarebbe meglio invece chiamare Fiddle su lui Frobble in the Janitor?


Riguardo al tuo "Non mi piace questa frase" - in realtà ho pensato brevemente di farlo in quel modo quando ho formulato la domanda, ma non volevo ingombrare la semantica della mia domanda. Ma sono d'accordo con te. Tipo. :)
Johann Gerell

1
Votato per aver fornito un esempio dalla stessa Microsoft. Nota che c'è un'eccezione specifica da lanciare nel caso che menzioni: ObjectDisposedException.
Antitoon

4

Abbiamo un ampio uso di questo modello nella nostra base di codice e l'ho già visto dappertutto - sono sicuro che deve essere stato discusso anche qui. In generale non vedo cosa c'è di sbagliato in questo, fornisce uno schema utile e non causa alcun danno reale.


4

Intervenendo su questo: sono d'accordo con la maggior parte qui che è fragile, ma utile. Vorrei indirizzarti alla classe System.Transaction.TransactionScope , che fa qualcosa come vuoi tu.

In generale mi piace la sintassi, rimuove un sacco di confusione dalla vera carne. Per favore, considera di dare alla classe helper un buon nome - forse ... Scope, come l'esempio sopra. Il nome dovrebbe far capire che incapsula un pezzo di codice. * Scope, * Block o qualcosa di simile dovrebbe farlo.


1
Il "bidello" proviene da un vecchio articolo in C ++ di Andrei Alexandrescu sulle protezioni dell'ambito, anche prima che ScopeGuard arrivasse a Loki.
Johann Gerell

@ Benjamin: mi piace la classe TransactionScope, non ne avevo mai sentito parlare prima. Forse perché sono in .Net CF land, dove non è incluso ... :-(
Johann Gerell

4

Nota: il mio punto di vista è probabilmente influenzato dal mio background C ++, quindi il valore della mia risposta dovrebbe essere valutato rispetto a quel possibile bias ...

Cosa dice la specifica del linguaggio C #?

Citando la specifica del linguaggio C # :

8.13 L'istruzione using

[...]

Una risorsa è una classe o una struttura che implementa System.IDisposable, che include un singolo metodo senza parametri denominato Dispose. Il codice che utilizza una risorsa può chiamare Dispose per indicare che la risorsa non è più necessaria. Se Dispose non viene chiamato, alla fine si verifica l'eliminazione automatica come conseguenza della garbage collection.

Il codice che utilizza una risorsa è, ovviamente, il codice che inizia dalla usingparola chiave e va fino all'ambito associato al using.

Quindi immagino che questo sia OK perché la serratura è una risorsa.

Forse la parola chiave è usingstata scelta male. Forse avrebbe dovuto essere chiamato scoped.

Quindi, possiamo considerare quasi tutto come una risorsa. Un file handle. Una connessione di rete ... Un filo?

Un filo???

Usando (o abusando) la usingparola chiave?

Sarebbe brillante (ab) usare la usingparola chiave per assicurarsi che il lavoro del thread sia terminato prima di uscire dall'ambito?

Herb Sutter sembra pensare che sia brillante , poiché offre un uso interessante del pattern IDispose per aspettare che il lavoro di un thread finisca:

http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095

Ecco il codice, copiato dall'articolo:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

Sebbene il codice C # per l'oggetto Active non sia fornito, è scritto che il C # utilizza il modello IDispose per il codice la cui versione C ++ è contenuta nel distruttore. E guardando la versione C ++, vediamo un distruttore che attende la fine del thread interno prima di uscire, come mostrato in questo altro estratto dell'articolo:

~Active() {
    // etc.
    thd->join();
 }

Quindi, per quanto riguarda Herb, è brillante .


3

Credo che la risposta alla tua domanda sia no, questo non sarebbe un abuso di IDisposable.

Il modo in cui capisco l' IDisposableinterfaccia è che non dovresti lavorare con un oggetto una volta che è stato eliminato (tranne per il fatto che ti è permesso chiamare il suo Disposemetodo tutte le volte che vuoi).

Dal momento che ne crei una nuova FrobbleJanitor esplicitamente ogni volta che arrivi usingall'istruzione, non usi mai lo stesso FrobbeJanitoroggetto due volte. E poiché il suo scopo è gestire un altro oggetto, Disposesembra appropriato al compito di liberare questa risorsa ("gestita").

(A proposito, il codice di esempio standard che dimostra la corretta implementazione di Disposesuggerisce quasi sempre che anche le risorse gestite dovrebbero essere liberate, non solo le risorse non gestite come gli handle del file system.)

L'unica cosa di cui mi preoccuperei personalmente è che è meno chiaro cosa sta succedendo using (var janitor = new FrobbleJanitor())rispetto a un try..finallyblocco più esplicito in cui le operazioni Lock& Unlocksono direttamente visibili. Ma quale approccio viene adottato probabilmente si riduce a una questione di preferenze personali.


1

Non è offensivo. Li stai usando per quello per cui sono stati creati. Ma potresti dover considerare l'uno sull'altro in base alle tue esigenze. Ad esempio, se stai optando per "abilità artistica", puoi usare "using" ma se il tuo pezzo di codice viene eseguito molte volte, allora per motivi di prestazioni potresti usare i costrutti "try" .. "finalmente". Perché "usare" di solito implica la creazione di un oggetto.


1

Penso che tu l'abbia fatto bene. L'overload di Dispose () sarebbe stato un problema che la stessa classe in seguito ha dovuto eseguire per la pulizia, e la durata di quella pulizia è cambiata per essere diversa dal momento in cui ci si aspetta di mantenere un blocco. Ma dal momento che hai creato una classe separata (FrobbleJanitor) che è responsabile solo del blocco e dello sblocco di Frobble, le cose sono abbastanza disaccoppiate da non riscontrare quel problema.

Rinominerei FrobbleJanitor, probabilmente in qualcosa come FrobbleLockSession.


Informazioni sulla ridenominazione: ho utilizzato "Janitor" come motivo principale per cui mi sono occupato di Lock / Unlock. Pensavo facesse rima con le altre scelte di nome. ;-) Se usassi questo metodo come modello in tutta la mia base di codice, includerei sicuramente qualcosa di più genericamente descrittivo, come hai suggerito con "Session". Se fossero stati i vecchi tempi del C ++, probabilmente avrei usato FrobbleUnlockScope.
Johann Gerell
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.