使用和Dispose()滥用范围处理不被释放的对象?

时间:2013-08-07 18:09:44

标签: c# using anti-patterns

出于方便和安全的原因,我想使用using语句来分配和释放来自/到池的对象

public class Resource : IDisposable
{
    public void Dispose()
    {
        ResourcePool.ReleaseResource(this);
    }
}

public class ResourcePool
{
    static Stack<Resource> pool = new Stack<Resource>();

    public static Resource GetResource()
    {
        return pool.Pop();
    }

    public static void ReleaseResource(Resource r)
    {
        pool.Push(r);
    }
}

和访问池一样

using (Resource r = ResourcePool.GetResource())
{
     r.DoSomething();
}

我找到了一些关于滥用usingDispose()进行范围处理的主题,但所有主题都包含using (Blah b = _NEW_ Blah())
这里的对象在离开使用范围后不会被释放,而是保留在池中 如果using语句只是扩展为普通try finally Dispose(),那么这应该可以正常工作但是在幕后会发生更多的事情,或者在未来的.Net版本中这种情况不会发生吗?

6 个答案:

答案 0 :(得分:7)

这根本不是滥用 - 这是C#的常见范围处理习惯用语。例如,ADO.NET对象(连接,语句,查询结果)通常包含在using块中,即使其中一些对象在其Dispose方法中被释放回其池中:

using (var conn = new SqlConnection(dbConnectionString)) {
    // conn is visible inside this scope
    ...
} // conn gets released back to its connection pool

答案 1 :(得分:2)

这是使用IDisposable的有效方式。

实际上,这也是在.NET中完成连接池的方式 - 在using语句中包装DBConnection对象以确保连接关闭并返回到连接池。

TransactionScope是使用Dispose模式回滚未完成事务的类的另一个示例:

  

对Dispose方法的调用标志着事务范围的结束。

答案 2 :(得分:2)

  

如果using语句只是简单地扩展到一个普通的尝试,最后Dispose()这应该可以正常工作但是在幕后会发生更多的事情,或者在将来的.Net版本中这种情况不会发生吗?

确实如此。您的代码应该可以正常工作,并且由规范保证以相同的方式继续工作。实际上,这是相当常见的(在SQL中查看连接池,这是一个很好的例子。)

您编写的代码的主要问题是,您可以在ReleaseResource内明确调用using,这可能会导致池被多次推送资源,因为它是公共API。

答案 3 :(得分:2)

这看起来像是对IDisposable的滥用以及对我的糟糕设计决定。首先,它强制存储在池中的对象了解池。这类似于创建一个List类型,该类型强制其中的对象实现特定接口或从某个特定类派生。就像LinkedList类一样,它强制数据项包含列表可以使用的NextPrevious指针。

此外,您让池为您分配资源,但是资源有一个调用将自己放回池中。这看起来很奇怪。

我认为更好的选择是:

var resource = ResourcePool.GetResource();
try
{
}
finally
{
    ResourcePool.FreeResource(resource);
}

这是一个更多的代码(尝试/最终而不是using),但更简洁的设计。它可以减轻所包含的对象对容器的了解,并且更清楚地表明池正在管理对象。

答案 4 :(得分:1)

您对using声明的理解是正确的(tryfinallyDispose)。我不认为这种变化很快就会发生。如果有的话会有很多事情发生。

您计划的内容并不一定有任何问题。我以前见过这种事情,Dispose实际上没有关闭对象,而是把它放到某种不完全可操作的状态。

如果您对此感到担忧,可以通过遵循通常的Dispose实现模式的方式实现此目的。只需要一个实现IDisposable的包装类,并公开底层类的所有方法。处理包装器时,将底层对象放入池中,而不是包装器。然后你可以考虑关闭包装器,虽然它包装的东西不是。

答案 5 :(得分:0)

你在做什么就像C ++和RAII。在C#中,它与您可以得到的C ++ / RAII习惯用语非常接近。

知道关于C#的事情或者两件事的Eric Lippert坚决反对使用IDispose和使用语句作为C#RAII习语。请在此处查看他的深度回复Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?

此RAII方式中使用的IDisposable的部分问题是IDisposable具有非常严格的要求才能正确使用。几乎所有使用IDisposable的C#代码都无法正确实现模式。 Joe Duffy撰写了一篇博文,详细介绍了实现IDisposable模式http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/的正确方法。 Joe的信息比MSDN上提到的更加详细和广泛。 Joe也知道关于C#的一件事或两件事,并且有很多非常聪明的贡献者帮助充实了这个文档。

可以通过简单的方法来实现简单的最小IDisposable模式(例如在RAII中使用),例如密封类,并且因为没有没有终结器的非托管资源等等。 MSDN https://msdn.microsoft.com/en-us/library/system.objectdisposedexception%28v=vs.110%29.aspx是一个很好的概述,但Joe的信息包含了所有的血腥细节。

但是你无法摆脱IDisposable的一件事就是它的病毒&#34;性质。保留IDisisable成员的类本身应该成为IDisposable ......在using(RAII raii = Pool.GetRAII())场景中不是问题,但需要注意的事项。

所有这一切,尽管埃里克的职位(我倾向于在大多数其他事情上与他达成一致),以及乔的50篇关于如何正确实施IDisposable模式的文章......我自己用它作为C#/ RAII的习语。

现在只有当C#有1个不可空的引用(比如C ++或D或Spec#)和2)深度不可变的数据类型(比如D,甚至F#[你可以做F#类的不可变的C#,但它有很多样板,它太难以正确......使得很难,而难以理解 ] 3)按合同设计作为语言本身的一部分(如D或Eiffel或Spec#,而不是C#Code Contracts憎恶)。 叹气也许C#7。

相关问题