什么是安全返回构建的IDisposables的最佳方法?

时间:2009-12-16 15:26:51

标签: c# idisposable

编辑: 下面显示了两个选项。

如果您只是使用 IDisposable提供的功能,则恰当命名的using子句可以正常工作。如果你在一个对象中包装一个IDisposable,那么包含的对象本身必须是IDisposable,你需要实现适当的模式(密封的IDisposable } class,或者更混乱但是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 个答案:

答案 0 :(得分: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);

但是,我不确定这是否真的是一种更好的方式。

答案 1 :(得分: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();
     }
}

它不会捕获并重新抛出错误。

答案 2 :(得分: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;

答案 3 :(得分: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变量,并且该对象将无法正确处理。