return IDisposable 对象的最佳方式并且仅在出现异常时处理?

Best way to return IDisposable object and only disposing on Exception?

我一直在寻找这方面的答案,但我无法真正找到我正在实施的确切案例。我想 return IDisposable 对象在一个方法中没有 Disposing/Closing 在同一个方法中。但我只想在引发异常时处理该对象。在这种情况下,最好的处理方法是什么?以下是我目前处理这个案例的方式。

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

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

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

您所展示的当前方法,无法创建实例,例如测试,而不创建实际连接。另外,是不是方法不透明,别人不会假设连接打开了。

我发布的方法使您的代码的用户能够更轻松地处理打开 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;
        } 
    }

正如 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();
    }
}