簡體   English   中英

RAII在C#中是否可以安全使用?和其他垃圾收集語言?

[英]Is RAII safe to use in C#? And other garbage collecting languages?

我正在制作一個RAII類,它接受一個System.Windows.Form控件,並設置它的光標。 在析構函數中,它將光標設置回原來的狀態。

但這是個壞主意嗎? 當這個類的對象超出范圍時,我可以安全地依賴析構函數嗎?

這是一個非常非常糟糕的主意。

當變量超出范圍時, 不會調用終結器。 它們在對象被垃圾收集之前的某個時刻被調用,這可能是很長一段時間之后。

相反,您想要實現IDisposable ,然后調用者可以使用:

using (YourClass yc = new YourClass())
{
    // Use yc in here
}

這將自動調用Dispose

在C#中很少需要終結器 - 只有在您直接擁有非托管資源(例如Windows句柄)時才需要它們。 否則,您通常會有一些托管包裝類(例如FileStream ),如果需要,它將具有終結器。

請注意,只有在需要清理資源時才需要其中任何一項 - .NET中的大多數類都不實現IDisposable

編輯:只是約嵌套評論作出回應,我同意它可以是一個有點難看,但你不應該需要using以我的經驗發言非常頻繁。 你可以像這樣垂直嵌套使用,如果你有兩個直接相鄰的:

using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
    ...
}

你知道,很多聰明的人說“如果你想在C#中實現RAII,就使用IDisposable”,而我只是不買它。 我知道我在這里是少數,但是當我看到“使用(blah){foo(blah);}”時,我自動認為“blah包含一個非托管資源,需要在foo完成后立即進行清理(或投擲)以便其他人可以使用該資源“。 我不認為“blah不包含任何有趣的內容,但是需要發生一些語義上重要的突變,我們將通過字符'}'表示語義上重要的操作” - 某些突變就像某些堆棧必須彈出或者某些標志必須重置或其他。

我說如果你有一個語義上重要的操作,必須在某些事情完成時完成,我們有一個單詞,那個單詞是“finally”。 如果操作很重要 ,那么它應該表示為一個語句,你可以在那里看到並設置一個斷點,而不是一個右大括號的隱形副作用。

那么讓我們考慮一下你的特定操作。 你想代表:

var oldPosition = form.CursorPosition;
form.CursorPosition = newPosition;
blah;
blah;
blah;
form.CursorPosition = oldPosition;

那段代碼非常清楚 所有副作用都在那里,對於想要了解代碼正在做什么的維護程序員來說是可見的。

現在你有一個決策點。 怎么回事呢? 如果blah拋出然后發生意外的事情 你現在不知道“形式”是什么狀態; 它可能是“形式”中的代碼。 它可能是一些突變的中途,現在處於一些完全瘋狂的狀態。

鑒於這種情況,你想做什么?

1)將問題提交給其他人。 沒做什么。 希望調用堆棧中的其他人知道如何處理這種情況。 表格已經處於不良狀態的原因; 光標不在正確位置的事實是它最不擔心的事實。 不要盯着已經如此脆弱的東西,據報道曾經有過例外。

2)將光標重置在finally塊中,然后將問題報告給其他人。 希望 - 沒有任何證據證明你的希望將會實現 - 將光標重置在你知道處於脆弱狀態的表單上本身不會引發異常。 因為,那種情況會發生什么? 可能有人知道如何處理的原始異常丟失了。 您已經破壞了有關問題原始原因的信息,這些信息可能是有用的。 而且你已經用一些關於光標移動故障的其他信息取而代之,這對任何人都沒用。

3)編寫處理(2)問題的復雜邏輯 - 捕獲異常,然后嘗試在單獨的try-catch-finally塊中重置光標位置,該塊抑制新異常並重新拋出原始異常。 這可能很難做到,但你可以做到。

坦率地說,我的信念是,正確答案幾乎總是(1)。 如果出現了可怕的問題,那么你無法安全地推斷脆弱國家的進一步突變是合法的。 如果您不知道如何處理異常,那么放棄。

(2)是帶有使用塊的RAII給你的解決方案。 同樣,我們首先使用塊的原因是在不再使用塊時積極清理重要資源。 無論是否發生異常,都需要快速清理這些資源 ,這就是“使用”塊具有“最終”語義的原因。 但“最終”語義不一定適用於非資源清理操作的操作; 正如我們所看到的,程序語義 - 負載最終阻止隱含地假設清理總是安全的; 但我們處於異常處理狀態的事實表明它可能不安全。

(3)聽起來很多工作。

所以總之,我說停止嘗試這么聰明。 你想改變光標,做一些工作,並取消突變光標。 所以寫完三行代碼就完成了。 沒有華麗的褲子RAII是必要的; 它只是增加了不必要的間接性,它使得閱讀程序變得更加困難,並且它使得它在特殊情況下可能脆,而不是更脆弱。

編輯:顯然,Eric和我對這種用法存在分歧。 :○

你可以使用這樣的東西:

public sealed class CursorApplier : IDisposable
{
    private Control _control;
    private Cursor _previous;

    public CursorApplier(Control control, Cursor cursor)
    {
        _control = control;
        _previous = _control.Cursor;
        ApplyCursor(cursor);
    }

    public void Dispose()
    {
        ApplyCursor(_previous);
    }

    private void ApplyCursor(Cursor cursor)
    {
        if (_control.Disposing || _control.IsDisposed)
            return;

        if (_control.InvokeRequired)
            _control.Invoke(new MethodInvoker(() => _control.Cursor = cursor));
        else
            _control.Cursor = cursor;
    }
}

// then...

using (new CursorApplier(control, Cursors.WaitCursor))
{
    // some work here
}

如果你想在C#中做類似RAII的事情,請使用IDisposable模式

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM