【问题标题】:Best way to return IDisposable object and only disposing on Exception?返回 IDisposable 对象并仅处理异常的最佳方法?
【发布时间】:2018-06-15 23:57:57
【问题描述】:

我一直在寻找这个问题的答案,但我真的找不到我正在实施的确切案例。我想在一个方法中返回 IDisposable 对象,而不在同一方法中处理/关闭。但我只想在引发异常时处理对象。在这种情况下,最好的处理方法是什么?以下是我目前处理此案的方式。

public DisposableObject GetObject()
{
   DisposableObject object = null;

   try
   {
      object = new DisposableObject();
      object.Open();
   }
   catch
   {
      if (object != null)
      {
         object.Close();
      }
      throw;
   }
   return object;
}

从上述方法创建的对象将被类似这样的其他方法消耗和处置。

public void OtherMethod()
{
  using(var object2 = GetObject())
  {
    DoSomethingWithObject2(object2);
  }
}

在这种情况下还有其他选择吗?

【问题讨论】:

  • 您不应该在catch 中调用object.Dispose() 吗?
  • 我似乎无法弄清楚为什么有人想要这样做。此外,看起来删除 throw 将允许它返回,但不会处理异常。
  • 你能提供一个真实的例子吗?此代码甚至不是有效的 C#。如果我们知道您想要完成什么而不是您尝试如何实现它,那么提供答案可能会更容易。
  • 这看起来很 XY-ish。一个真实的例子会很棒。

标签: c# exception exception-handling idisposable


【解决方案1】:

正如 Morton 所说,问题在于在工厂方法中对 DisposableObject 实例调用 Open,但我不同意相应的解决方案。

真正的问题是Open 是一个泄漏的抽象。为什么我要实例化一个无效的实例?当我想要一个文件流时,我调用File.Open(...),我不实例化一个新的Stream 并调用OpenFile(...)。这将迫使我知道如何打开文件,并完全破坏工厂方法的目的(是的,File.OpenStream 实例的工厂方法)。

所以当我读一个文件时,我是这样读的:

using (var stream = File.Open("myFile.text", FileMode.Open))
using (var reader = new StreamReader(stream))
{
    var content = reader.ReadToEnd();

    Console.WriteLine(content);
}

如果文件无法正确打开,我不必担心如何关闭文件。 事实上,如果Open调用抛出异常,using语句甚至不能在stream上调用Dispose(),因为流还没有被分配,执行流还没有' 甚至还没有进入using 语句的块作用域。

public class Program
{
    public void Main()
    {
        try
        {
            using (var obj = MyObj.Create())
            {
                // everything is already in a valid state.
                var message = obj.ReadMessage();

                Console.WriteLine(message);
            }
        }
        catch (Exception ex)
        {
            // If the factory succeeded, the using statement did the cleanup.
            // if the factory failed, the factory took care of the cleanup.
            Console.WriteLine(ex.Message);
        }
    }
}

public class MyObj : IDisposable
{
    public static MyObj Create()
    {
        Stream stream = null;
        StreamReader reader = null;

        try
        {
            stream = File.Open("myFile.txt", FileMode.Create);
            reader = new StreamReader(stream);

            return new MyObj(reader);
        }
        catch
        {
            reader?.Dispose();
            stream?.Dispose();

            throw;
        }
    }

    private readonly StreamReader _reader;

    private MyObj(StreamReader reader)
    {
        _reader = reader;
    }

    public string ReadMessage()
    {
        return "the message: " + _reader.ReadToEnd();
    }

    public void Dispose()
    {
        _reader?.Dispose();
    }
}

【讨论】:

    【解决方案2】:

    我想我宁愿把打开和关闭连接的所有责任推给这个对象的用户,而不是工厂。

    您显示的当前方法,无法创建实例,例如测试,而不创建实际的连接。此外,该方法是否不透明,因为其他人不会假设要打开连接。

    我发布的方法使您的代码用户能够更轻松地处理打开 DisposableObject 时可能发生的错误。

        public DisposableObject GetObject() {
            return new DisposableObject();
        }
    
        public void OtherMethod() {
            using (DisposableObject o = GetObject()) {
                try {
                    o.Open();
                } catch (Exception ex) {
                    // Log(ex);
                } finally {
                    // If not called within the dispose function.
                    o.Close();
                }
            }
        }
    

    不过,如果你想处理开口,你的方法是可以的。不过,我会更改一些小细节:

        public DisposableObject GetObject() {
            var o = new DisposableObject();
            try {                
                o.Open();
                return o;
            } catch (Exception ex) {
                o.Dispose(); // o.Close is called within.
                throw ex;
            } 
        }
    

    【讨论】:

    • o 处于无效状态,而工厂方法使这种情况成为可能。您的解决方案是一种反模式。
    猜你喜欢
    • 2020-03-05
    • 1970-01-01
    • 1970-01-01
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多