2009-12-16 35 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的通用方法。 –

相关问题