代码之家  ›  专栏  ›  技术社区  ›  Eamon Nerbonne

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

  •  8
  • Eamon Nerbonne  · 技术社区  · 15 年前

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

    如果你只是 使用 IDisposable提供的功能,适当命名 using 条款有效。如果你是 包装 IDisposable 在对象中,包含对象本身需要 可识别 您需要实现适当的模式(或者 不可分的 阶级,或者更混乱的但是 standard virtual pattern )。

    但有时辅助工厂的方法有利于清洁。如果您返回 不可分的 直接在构造之后,您就可以了,但是如果您首先构造它,然后修改它,或者在返回之前执行可以抛出异常的代码,那么您需要安全地调用 .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 回复  |  直到 15 年前
        1
  •  4
  •   Jeffrey L Whitledge    15 年前

    我认为这是标准模式:

    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
  •  7
  •   Matthias    15 年前

    不-我想没有更好的方法。

    但是,您可以编写一个助手类:

    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);
    

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

        3
  •  2
  •   Mark Seemann    15 年前

    你可以考虑写一篇 扩展方法 :

    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;
    
        4
  •  0
  •   Eamon Nerbonne    15 年前

    我认为你对这个问题过于复杂了。

    如果您的方法返回一个可弃对象,那么您将说“我在此放弃对该对象的所有权,不管是好是坏”。如果在构建过程中发生了错误,那么为什么会有区别呢?即使抛出异常,调用代码仍将处理它。

    例如:

    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变量,也不会正确释放该对象。

    推荐文章