2010-02-16 17 views
5

C#是否有辦法臨時更改特定範圍內變量的值,並在範圍/塊的末尾自動恢復它?C#是否有辦法在小範圍內模仿軟件事務內存?

例如(不是真正的代碼):

bool UpdateOnInput = true; 

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

編輯:

我想以上的原因是因爲我不想這樣做:

UpdateOnInput = false 

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

UpdateOnInput = true 
+0

軟件事務內存? – shahkalpesh

+4

傷心地看到你有一個非常好的代表,並且仍然將整個問題作爲標題發佈。 – Shoban

+2

你能詳細說明你需要什麼這個構造?發生這種情況時,會自動通過值傳遞變量,例如進行函數調用時,但我不確定這是您正在尋找的。 – GrayWizardx

回答

13

沒有,有沒有辦法直接做到這一點。關於如何做這種事情,有幾種不同的思想流派。比較和對比這兩個:

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

VS

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

很多人會告訴你,後者則是 「更安全」。

這不一定如此。

兩者之間的差異當然是即使DoSomething()拋出異常,後者也會恢復狀態。那是更好比在異常情況下保持狀態變異? 是什麼讓它更好?你有一個意外的,未處理的異常報告,可怕的意外的已經發生。你的內部狀態可能會完全不一致和任意混亂;沒有人知道在例外情況下可能發生了什麼。我們所知道的是,DoSomething可能正試圖對變異狀態做些事情。

在發生可怕和未知事件的情況下,是否真的是正確的做法,不斷攪動那個特定的罐子,並嘗試改變剛剛導致異常的狀態再次

有時這將是做正確的事,有時它會更糟。你實際使用哪種場景取決於代碼的完成情況,因此仔細考慮在盲目選擇其中一個之前做正確的事情。

坦率地說,我寧願沒有取得進入擺在首位的情況下解決問題。我們現有的編譯器設計使用了這種設計模式,坦率地說,它是令人生氣的。在現有的C#編譯器中,錯誤報告機制是「副作用」。也就是說,當編譯器的一部分出現錯誤時,它會調用錯誤報告機制,然後將錯誤顯示給用戶。

這是拉姆達結合的一個主要問題。如果您有:

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

那麼這個工作的方式是,我們嘗試綁定

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

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

,我們看看哪一個,如果有的話,給了我們一個錯誤。在這種情況下,前者給出錯誤,後者編譯時沒有錯誤,所以這是合法的lambda轉換,重載解析成功。 我們如何處理錯誤?我們無法向用戶報告,因爲此程序無錯誤!

我們做什麼。因此,當我們綁定一個lambda的身體是你說什麼:我們告訴了記者的錯誤「不報你的錯誤,給用戶;他們在這個緩衝區保存在這裏,而不是」。然後我們綁定lambda,將錯誤記錄器恢復到它以前的狀態,並查看錯誤緩衝區的內容。

我們可以完全避免這個問題,通過改變表達分析儀,使其與結果一起返回的錯誤,而不是讓錯誤的狀態有關的副作用。然後,對錯誤報告狀態進行變更的需求完全消失,我們甚至不必擔心。

所以我會鼓勵你重新審視你的設計。有沒有一種方法可以使您正在執行的操作不依賴於您正在變異的狀態?如果是這樣,那麼就這樣做,然後你不必擔心如何恢復變異狀態。 (當然在我們的例子中,我們的想要在異常時恢復狀態如果在lambda綁定過程中編譯器內部的某個東西會拋出,我們希望能夠向用戶報告這一點!我們不需要希望錯誤記者留在「壓制報告錯誤」狀態。)

+1

我看到它的方式,如果期望異常,因此處理,'finally'塊應該運行並且回退任何狀態更改以恢復已知狀態良好。如果異常意外,就不應該處理,程序應該終止而不運行finally塊:可以通過處理AppDomain.UnhandledException並調用Environment.FailFast來安排。通過這種方法,「finally」塊總是毫不費力的:它們可以根據需要安全地寫入展開狀態。它們執行的控制因素是是否有例外被捕獲。 –

+4

有一天有人應該嘗試從你在這裏和你的博客中提到的小塊裏反向工程C#編譯器...... –

+0

謝謝埃裏克。在我的例子中,假設檢測到一些變化的系統,並且該方法所做的工作不應該被檢測到,那麼您將如何設計該方法不依賴於change_detection_system使用的那個狀態?它需要一個非常不同的想法嗎? –

5

無,你必須用try/finally塊手動完成。我敢說你可能寫一個IDisposable實現這將做一些hacky與lambda表達式結合,但我懷疑try/finally塊更簡單(並且不abuse the using statement)。

12

沒有,但它是非常簡單的,只是這樣做:

bool UpdateOnTrue = true; 

// .... 

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

聽起來像是你真的想

Stack<bool> 
+0

你可以多走一步,把代碼放在一個函數中並傳遞變量作爲一個參數,然後堆棧管理爲您而不用考慮它。 – AaronLS

+1

這簡直太簡單了。被設置的變量可以是一個字段而不是一個局部變量,該點將停止重新進入代碼塊。有時可以忽略消息(例如,鼠標移動事件),因爲消息到達時對象被「使用」。 –

1

罐頭......我對此表示懷疑。鑑於您的例子是一個簡單的使用臨時布爾值,我假設你已經得到的東西記古怪:-)你可以實現某種協議棧stucture的:

1)按舊值到堆棧

2)裝入新的值

3)做的東西

4)從棧中彈出並且更換用過的值。

粗糙(AKA未經測試)例(不能查找棧語法現在)

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

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

謝謝,但由於舊價值,你的意思是假的或真的在我的例子?你能用代碼表明這一點嗎? –

+1

@Joan Venge添加了一個粗略的代碼示例來展示我在說什麼 –

6

嘗試:

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

現在,你可以說:

bool flag = false; 

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

// flag is false again 
+0

+1正是我會寫的。 'ref'和lambda表達式的完美結合。 –

+0

謝謝,很好的例子。 –

1

你應該重構你的代碼使用一個單獨的功能,像這樣:

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

對於這個爲參考類型工作,您需要先複製它:

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

當變量的值變化很大時,很難寫出正確的代碼。力爭在代碼中儘可能少地重新分配。