返回一个可用于 using 块的可释放对象

5

如何在函数中返回一个可被释放的对象以确保它可以在using块内正常工作?在我的函数中,我想要对可被释放的对象进行操作并处理错误,这使得问题变得复杂。

到目前为止,我的代码类似于以下内容:

DBHandle GetDB()
{
/*  // I can't do this, because the using block would dispose of my object within this function
    using( var db = DatabaseObj.GetHandle() )
    {
        db.Open();
        return db;
    }
*/
    var db = DatabaseObj.GetHandle();
    try
    {
        db.Open();
        return db;
    }
    catch (Exception ex)
    {
        db.Dispose();
        throw ex;
    }
}

// In other code:
using( var obj = GetDB() ){ /* ... */ }

编辑:发布了一个与此类似的更普遍的问题,以避免混淆答案和讨论。


1
我认为我对你更一般的问题的回答已经很好地总结了,而你上面的代码看起来对我来说是正确的。 - Gregory Higley
5个回答

9
提示:当从using块返回一个可清理对象时,请记住,在执行返回语句时调用Dispose()方法!!!

因此,从using块返回的对象在离开函数时已经被处理。

以下代码是一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class MyDisposable : IDisposable
    {
        public void DoSomething()
        {
            Console.WriteLine("  In DoSomething");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Console.WriteLine("  In Dispose");
        }

        #endregion
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Main\n");

            Console.WriteLine("Before NormalMethod");
            NormalMethod();
            Console.WriteLine("After NormalMethod\n");

            Console.WriteLine("Before ReturningMethod");
            MyDisposable m = ReturningMethod();
            m.DoSomething(); // Here the object already has been disposed!
            Console.WriteLine("After ReturningMethod\n");

        }

        private static void NormalMethod()
        {
            using (MyDisposable myDisposable = new MyDisposable())
            {
                Console.WriteLine("  In NormalMethod");
            }
            return;
        }

        private static MyDisposable ReturningMethod()
        {
            using (MyDisposable myDisposable = new MyDisposable())
            {
                Console.WriteLine("  In ReturningMethod");
                return myDisposable;
            }
        }
    }
}

这将会产生以下输出: enter image description here

3

'Using'是为你完成try/catch工作,只需使用db.Open; using将确保无论是否抛出异常,都会处理连接。


没错,但是如果GetDB函数没有遇到任何异常,我不想在其中释放db。因此,我不能在GetDB中使用using - palswim
这不是 'using' 的工作方式。相反,您需要一个工厂,只要对象仍然有效,就可以重复使用它。 - Ana Betts
实际上,相比try..catchtry..finally更好,因为异常并未被处理,顺带一提这正是using块所做的。因此,using块已经完成了工作,并且做得更好。 :) - Guffa
@palswim:在GetDB方法中你不需要使用 using,也不需要 try...catch。只需获取连接、打开它并返回即可。调用GetDBusing块将始终处理该对象的释放。 - Guffa
@Guffa:我猜我不知道 try..catch 的内部工作。你是说,即使我的函数没有到达 return 行,更高级别的 using 语句仍然能够正确处理对象的处理?(在我看来,它会创建一个 NullReferenceException 或其他什么东西。) - palswim
显示剩余2条评论

3
你的思路是正确的,但似乎对于它为何正确有些迷茫。
考虑一下你所说的不能运行的代码:
DBHandle GetDB()
{
    using( var db = DatabaseObj.GetHandle() )
    {
        db.Open();
        return db;
    }
}

这段代码与以下代码几乎等价:

DBHandle GetDB()
{
    var db = DatabaseObj.GetHandle();
    try
    {
      db.Open();
      return db;
    }
    finally
    {
        if(db != null)//not included if db is a value-type
          ((IDisposable)db).Dispose();
    }
}

需要翻译的内容如下:

需要注意的几点包括try只有在赋值之后才会执行(同样适用于using-它不能防止在using之前发生异常),db被转换为IDisposable,这意味着如果该赋值无效,则无法编译,并且Dispose()可以隐式或显式实现,两种方式都可以正常工作。

当然,finally块将执行无论是否发生异常。您不能使用using,因为它相当于finally,并且您希望仅在方法中发生异常时才Dispose()。因此,您将finally转换为catch:

DBHandle GetDB()
{
    var db = DatabaseObj.GetHandle();
    try
    {
      db.Open();
      return db;
    }
    catch
    {
        if(db != null)
          ((IDisposable)db).Dispose();
        throw;
    }
}

这基本上与您拥有的相同,除了增加了一个空值检查(也许您可以排除它的需要),并且我正在使用裸的 throw (通常是一个好主意,当您要重新抛出异常而不更改或检查它时。在某些情况下,抛出一个新的异常更好,在这种情况下,您应该将原始异常作为新异常的 InnerException 属性包含进去,以便为调试提供更多信息)。
总的来说,您走在了正确的道路上。希望我已经帮助解释清楚了原因。

2
如果DBHandle实现IDisposable接口,则您所拥有的应该可以工作。
没有"特殊"返回IDisposable的方式。

1

返回值只需实现IDisposable接口即可。

从实际角度来看,这个陈述必须成立:

IDisposable db = GetDB();

如果编译通过,你可以将GetDB()放在一个using语句中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接