2010-02-16 14 views
5

C# ha un modo per modificare temporaneamente il valore di una variabile in un ambito specifico e ripristinarlo automaticamente alla fine dell'ambito/blocco?C# ha un modo per simulare la memoria transazionale del software, su piccola scala?

Per esempio (non reale il codice):

bool UpdateOnInput = true; 

using (UpdateOnInput = false) 
{ 
    //Doing my changes without notifying anyone 
    Console.WriteLine (UpdateOnInput) // prints false 
} 
//UpdateOnInput is true again. 

EDIT:

Il motivo che voglio quanto sopra è perché io non voglio fare questo:

UpdateOnInput = false 

//Doing my changes without notifying anyone 
Console.WriteLine (UpdateOnInput) // prints false 

UpdateOnInput = true 
+0

Memoria transazionale software? – shahkalpesh

+4

triste per vedere che hai un ottimo rappresentante e ancora pubblicando l'intera domanda come titolo. – Shoban

+2

Puoi approfondire su cosa specificamente hai bisogno di questo costrutto? Ciò accade per le variabili passate per valore automaticamente come quando si effettuano chiamate di funzione, ma non sono sicuro che sia ciò che si sta cercando. – GrayWizardx

risposta

13

No, non c'è modo di farlo direttamente. Ci sono alcune diverse scuole di pensiero su come fare questo genere di cose. Confrontare e contrapporre questi due:

originalState = GetState(); 
SetState(newState); 
DoSomething(); 
SetState(originalState); 

vs

originalState = GetState(); 
SetState(newState); 
try 
{ 
    DoSomething(); 
} 
finally 
{ 
    SetState(originalState); 
} 

Molte persone vi diranno che il secondo è "più sicuro".

Non è necessariamente così.

La differenza tra i due è ovviamente il secondo ripristina lo stato anche se DoSomething() genera un'eccezione. È meglio rispetto a mantenere lo stato mutato in uno scenario di eccezione? Cosa lo rende migliore? Si è verificata un'eccezione imprevista e non gestita che segnala che qualcosa di orribile e è inatteso.Il tuo stato interno potrebbe essere completamente incoerente e arbitrariamente incasinato; nessuno sa cosa potrebbe essere accaduto al punto dell'eccezione. Tutto quello che sappiamo è che DoSomething stava probabilmente cercando di fare qualcosa allo stato mutato.

E 'davvero la cosa giusta da fare nello scenario in cui qualcosa di terribile e sconosciuto è accaduto a continuare a mescolare quel particolare pentola e cercando di mutare lo stato che appena ha causato un'eccezione nuovo?

A volte questa sarà la cosa giusta da fare e, a volte, peggiorerà le cose. Lo scenario in cui ti trovi effettivamente dipende da cosa sta facendo esattamente il codice, quindi pensa attentamente a quale sia la cosa giusta da fare prima di scegliere ciecamente l'uno o l'altro.

Francamente, preferirei risolvere il problema non entrando nella situazione in primo luogo. Il nostro design di compilatore utilizza questo modello di progettazione e, francamente, è irritante. Nel compilatore C# esistente il meccanismo di segnalazione degli errori è "effetto collaterale". Cioè, quando parte del compilatore riceve un errore, chiama il meccanismo di segnalazione degli errori che visualizza l'errore all'utente.

Questo è un grosso problema per il binding lambda. Se si dispone di:

void M(Func<int, int> f) {} 
void M(Func<string, int> f) {} 
... 
M(x=>x.Length); 

poi il modo in cui funziona è che cerchi di associare

M((int x)=>{return x.Length;}); 

e

M((string x)=>{return x.Length;}); 

e vediamo quale, se del caso, ci dà un errore. In questo caso, il primo dà un errore, il secondo si compila senza errori, quindi questa è una conversione lambda legale e la risoluzione del sovraccarico ha esito positivo. Cosa facciamo con l'errore? Non possiamo segnalarlo all'utente perché questo programma è privo di errori!

Quindi quello che facciamo quando leghiamo il corpo di un lambda è esattamente quello che dici: diciamo al reporter di errore "non segnalare i tuoi errori all'utente, salvali in questo buffer qui invece". Quindi leghiamo il lambda, ripristiniamo il reporter di errore allo stato precedente e guardiamo il contenuto del buffer degli errori.

Potremmo evitare completamente questo problema cambiando l'analizzatore di espressioni in modo che restituisse gli errori insieme al risultato, piuttosto che commettere errori di un effetto collaterale correlato allo stato. Quindi la necessità di modificare lo stato di segnalazione degli errori scompare del tutto e non dobbiamo nemmeno preoccuparcene.

Quindi ti incoraggerei a rivisitare il tuo design. C'è un modo per rendere l'operazione che stai eseguendo non dipende dallo stato che stai mutando? Se è così, allora fallo e non devi preoccuparti di come ripristinare lo stato mutato.

(E, naturalmente, nel nostro caso abbiamo facciamo vuole ripristinare lo stato su un'eccezione. Se qualcosa all'interno del compilatore genera durante vincolante lambda, vogliamo essere in grado di riferire che per l'utente! Noi non facciamo vuoi che il reporter di errore rimanga nello stato "sopprimere gli errori di segnalazione".)

+1

Il mio modo di vedere, se si prevede l'eccezione, quindi, manipolati, il blocco 'finally' dovrebbe funzionare e il vento di nuovo le modifiche allo stato di ripristinare un noto buono stato Se l'eccezione è inaspettata, non dovrebbe essere gestita e il programma dovrebbe terminare senza eseguire finalmente blocchi: questo può essere organizzato gestendo 'AppDomain.UnhandledException' e chiamando' Environment.FailFast'. Con questo approccio, i blocchi 'finally' sono sempre un gioco da ragazzi: possono essere scritti in modo sicuro per rilassarsi come richiesto. Il fattore che controlla se vengono eseguiti è se un'eccezione viene mai rilevata o meno. –

+4

Un giorno qualcuno dovrebbe provare a decodificare il compilatore C# dalle piccole pepite che hai citato qui e sul tuo blog ... –

+0

Grazie Eric. Nel mio esempio, per esempio per un sistema che rileva alcune modifiche e le cose che il metodo non dovrebbe essere rilevato, come lo progettereste che il metodo non si basa su quello stato che viene usato da change_detection_system? Richiede un pensiero molto diverso? –

5

No , devi farlo manualmente con un blocco try/finally. Oserei dire che potrebbe scrivere un'implementazione IDisposable che farebbe qualcosa di hacky in congiunzione con le espressioni lambda, ma sospetto che un blocco try/finally sia più semplice (e non abuse the using statement).

12

No, ma è abbastanza semplice da fare proprio questo:

bool UpdateOnTrue = true; 

// .... 

bool temp = UpdateOnTrue; 
try 
{ 
    UpdateOnTrue = false; 
    // do stuff 
} 
finally 
{ 
    UpdateOnTrue = temp; 
} 
4

Sembra che si desidera veramente

Stack<bool> 
+0

Puoi fare un passo in più e inserire il codice in una funzione e passare la variabile come parametro, quindi lo stack è gestito per te senza mai pensarci. – AaronLS

+1

Questo è semplicissimo. La variabile impostata può essere un campo piuttosto che una variabile locale, il punto è quello di smettere di rientrare nel blocco di codice. A volte va bene ignorare un messaggio (ad esempio un evento di spostamento del mouse) a causa di un oggetto "in uso" quando il messaggio è arrivato. –

1

in scatola ... ne dubito. Dato il vostro esempio è un semplice uso di un valore bool temporanea Sto assumendo che hai qualcosa in mente stravagante :-) È possibile implementare una sorta di Pila stucture:

1) Spingere vecchio valore sulla pila

2) Caricare il nuovo valore

3) fare cose

4) Pop dalla pila e sostituire il valore utilizzato.

ruvida (AKA testato) esempio (non può cercare la sintassi pila in questo momento)

bool CurrentValue = true; 
Stack<bool> Storage= new Stack<bool> 

Storage.Push(CurrentValue); 
CurrentValue=false; 
DoStuff(); 
CurrentValue = Storage.Pop(); 
//Continue 
+0

Grazie ma per vecchio valore intendi il falso o il vero nel mio esempio? Puoi per favore mostrarlo nel codice? –

+1

@Joan Venge Aggiunto un esempio di codice di massima per mostrare di cosa sto parlando –

6

Prova:

public void WithAssignment<T>(ref T var, T val, Action action) 
{ 
    T original = var; 
    var = val; 
    try 
    { 
     action(); 
    } 
    finally 
    { 
     var = original; 
    } 
} 

Ora si può dire:

bool flag = false; 

WithAssignment(ref flag, true,() => 
{ 
    // flag is true during this block 
}); 

// flag is false again 
+0

+1 Esattamente quello che avrei scritto. Perfetta combinazione di espressioni ref 'e lambda. –

+0

Grazie, bel esempio. –

1

Si dovrebbe refactoring del codice per utilizzare una funzione separata, in questo modo:

bool b = GetSomeValue(); 
DoSomething(ModifyValue(b)); 
//b still has the original value. 

Perché questo lavoro per un tipo di riferimento, è necessario copiarlo prima di scherzare con esso:

ICloneable obj = GetSomeValue(); 
DoSomething(ModifyValue(obj.Clone())); 
//obj still has the original value. 

È difficile scrivere codice corretto quando i valori delle variabili cambiano molto. Cerca di avere il minor numero possibile di riassegnazioni nel tuo codice.

Problemi correlati