2009-12-16 138 views
8

編輯:兩個選項如下所示。安全返回構建的IDisposables的最佳方式是什麼?

如果你只是使用 IDisposable提供的功能,恰當地命名爲using子句工作正常。如果你是IDisposable包含在一個對象中,則包含對象本身需要爲IDisposable,並且需要實現適當的模式(密封的IDisposable類或者messier,但是standard virtual pattern)。

但是有時候輔助工廠的方法對清潔很有好處。如果您在施工後直接返回IDisposable,則表示您沒有問題,但如果您先構建它,然後修改或執行可能會在返回之前拋出異常的代碼,則需要安全地撥打.Dispose() - 但只有,如果有的話一個錯誤。

例如,不安全的代碼看起來是這樣的......

DbCommand CreateCommandUnsafely(string commandText) 
{ 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; //what if this throws? 
    return newCommand; 
}  

解決方案兩個安全變種如下...

DbCommand CreateCommandSafelyA(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     success=true; 
     return newCommand; 
    } finally{ 
     if (!success && newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
    } 
} 


DbCommand CreateCommandSafelyB(string commandText) 
{ 
    DbCommand newCommand = null; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     return newCommand; 
    } catch { 
     if (newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
     throw; 
    } 
} 

安全的變體,A是僅有一個線長,但似乎是慣用的方法。似乎沒有任何真正簡潔的解決方案,儘管下面的一些海報給出了一些使用lambda的選項來提取封裝的邏輯。

與上述任何安全方法的代碼膨脹仍然存在,並且與原先看起來像代碼特別嚴重......

return new MyDisposableThing { 
    OptionA = "X", 
    OptionB = B.Blabla, 
    Values = src.Values.Where(priority => priority > 1.0), 
}; 

上面的代碼被安全地寫入是相當長的時間和更少的可讀因爲你不能再安全地使用縮短的setter語法。

回答

4

我相信這是標準模式:

DbCommand CreateCommand(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try 
    { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; 
     success = true; 
     return newCommand; 
    } 
    finally 
    { 
     if (!success & newCommand != null) 
      newCommand.Dispose(); 
    } 
} 

它沒有趕上並重新拋出的錯誤。

+0

這看起來像我在別處看到的代碼。你知道爲什麼這是可取的嗎? –

+1

捕捉任意異常(正如其他答案似乎倡導的那樣)會在特殊情況下引發各種問題,應儘可能避免。通過將處置置於'finally'語句中,標準模式避免捕捉異常,並且(當對象創建失敗時)它模仿標稱'using'語句的行爲。 –

7

不 - 我認爲沒有更好的方法。

然而,你可以寫一個輔助類:

public static class DisposeHelper 
{ 
    public static TDisposable DisposeOnError<TDisposable>(TDisposable dispoable, Action<TDisposable> action) 
    where TDisposable : IDisposable 
    { 
    try 
    { 
     action(dispoable); 
    } 
    catch(Exception) 
    { 
     disposable.Dispose(); 
     throw; 
    } 

    return disposable; 
    } 
} 

所以,你可以寫:

return DisposeHelper.DisposeOnError(connection.CreateCommand(), cmd => cmd.CommandText = commandText); 

我不知道,但是,如果這是真的一個更好的辦法。

2

你可以考慮寫一個擴展方法

public static class Disposable 
{ 
    public static void SafelyDo<T>(this T disp, Action<T> action) where T : IDisposable 
    { 
     try 
     { 
      action(disp); 
     } 
     catch 
     { 
      disp.Dispose(); 
      throw; 
     } 
    } 
} 

這將允許你寫這樣的代碼:

var disp = new MyDisposable(); 
disp.SafelyDo(d => 
    { 
     d.Foo = "Ploeh"; 
     d.Bar = 42; 
    }); 
return disp; 
0

我覺得你過於複雜的問題。

如果你的方法返回一個可丟棄的對象,那麼你就說「我在此放棄這個對象的所有權,好或壞」。如果在構建過程中發生錯誤,那麼爲什麼這會產生影響?即使拋出異常,調用代碼仍會處理它。

例如:

DbCommand CreateCommand(string commandText) { 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; // what if this throws? 
    return newCommand; 
} 

void UseCommand() { 
    using(var cmd = CreateCommand("my query goes here")) { 
     // consume the command 
    } 
} 

編輯:不幸的是,如果一個異常內部CreateCommand拋出,CMD變量永遠不會設置和對象將不會被正確地設置。

+3

我不相信這是真的。由於cmd變量永遠不會被分配,所以using塊將不會調用cmd.Dispose()。 –

+0

問題是CommandText上的throw將意味着newCommand永遠不會返回到using語句,所以它不會被丟棄。 – Kleinux

+0

好點,夥計們。在那種情況下,我贊成winSharp93的通用方法。 –

相關問題