8

出于方便和安全的原因,我想使用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 版本中不起作用?

4

6 回答 6

8

这根本不是滥用 - 这是 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
于 2013-08-07T18:14:02.603 回答
3

这是一种有效的使用方式IDisposable

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

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

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

于 2013-08-07T18:14:01.947 回答
2

如果 using 语句只是简单地扩展为一个普通的 try finally Dispose() 这应该可以正常工作,但是在幕后是否发生了更多事情,或者有可能在未来的 .Net 版本中不起作用?

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

正如所写的那样,您的代码的主要问题是您可以ReleaseResource在 a 内显式调用using,这可能会导致池多次推送资源,因为它是公共 API 的一部分。

于 2013-08-07T18:14:22.527 回答
2

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

此外,您让池为您分配资源,但随后资源会调用将其自身放回池中。这似乎……很奇怪。

我认为更好的选择是:

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

它的代码多一点(try/finally 而不是using),但设计更简洁。它使包含的对象不必了解容器,并且更清楚地表明池正在管理对象。

于 2013-08-07T18:48:57.910 回答
1

您对using陈述的理解是正确的 ( try, finally, Dispose)。我预计这种情况不会很快发生变化。如果这样做,很多事情都会崩溃。

你的计划不一定有什么问题。我以前见过这种事情,其中Dispose​​实际上并没有关闭对象,而是将其置于某种不“完全可操作”的状态。

如果您完全担心这一点,您可以按照通常的Dispose实现模式来实现它。只需有一个包装类来实现IDisposable和公开底层类的所有方法。当包装器被释放时,将底层对象放入池中,而不是包装器。然后您可以考虑关闭包装器,尽管它包装的东西没有。

于 2013-08-07T18:17:15.600 回答
0

你正在做的就像 C++ 和 RAII。在 C# 中,它与 C++/RAII 习语非常接近。

Eric Lippert 对 C# 略知一二,他坚决反对将 IDispose 和 using 语句用作 C# RAII 习语。在这里查看他的深入回应,使用 IDisposable 和“使用”作为获得“范围行为”以实现异常安全的手段是否滥用?.

以这种 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是一个很好的概述,但乔的信息包含所有血淋淋的细节。

但是,使用 IDisposable 无法摆脱的一件事是它的“病毒”性质。持有 IDisposable 成员的类本身应该成为 IDisposable ......这在using(RAII raii = Pool.GetRAII())场景中不是问题,但需要非常注意。

说了这么多,尽管 Eric 的立场(我倾向于在其他所有方面都同意他的观点),以及 Joe 的 50 页关于如何正确实施 IDisposable 模式的文章......我自己确实将它用作 C#/RAII 习语.

现在只有当 C# 有 1) 不可为空的引用(如 C++ 或 D 或 Spec#)和 2) 深度不可变的数据类型(如 D,甚至 F# [你可以在 C# 中执行 F# 类型的不可变,但它是很多样板,而且很难做到正确......使简单变得困难,而困难变得不可能])和 3)按合同设计作为语言的一部分(如 D 或 Eiffel 或 Spec#,而不是就像 C# Code Contracts 令人憎恶)。 叹息 也许是 C# 7。

于 2015-06-10T13:26:35.193 回答