代码之家  ›  专栏  ›  技术社区  ›  Trap

IDisposable对象是否只需创建和处理一次?

  •  2
  • Trap  · 技术社区  · 15 年前

    我使用的是一个巨大的二叉树型结构,它的节点可以使用也可以不使用非托管资源。其中一些资源可能占用大量内存,但一次只能使用其中的一些资源。树的初始状态可以看作是“休眠”。

    每当访问一个节点时,该特定节点及其子节点将“唤醒”并懒散地获取其分配的资源。同样,在树中访问不同的分支将使当前活动的分支进入睡眠状态,从而释放其资源。这意味着任何给定的节点都可以在任何给定的时间被唤醒并一次又一次地进入睡眠状态。

    我目前正在利用IDisposable接口来实现这一点。它非常有用,因为在很多情况下,我需要创建将在本地使用的小分支,“using”关键字非常方便,确保不会意外打开任何资源。

    我可以对那些不是真正被释放而是有点进入睡眠状态的对象实现IDisposable吗?

    事先谢谢。

    编辑:感谢所有聪明的答案。我喜欢处理资源访问而不是资源本身的想法。现在我正在为负责清理的职能部门寻找一个更好的名字。(除了release()或sleep()还有其他想法吗?再次感谢。

    6 回复  |  直到 15 年前
        1
  •  4
  •   Community CDub    8 年前

    @Jon Skeet answered the 问题真的很好,但让我插句话,我觉得应该是自己的答案。

    使用 using 代码块临时获取一些资源,或输入某种形式的范围代码,以便从中完全退出。我一直在这样做,特别是在我的业务逻辑控制器中,我有一个系统,可以将更改事件延迟到执行了一段代码,以避免多次副作用,或者在我准备好它们之前,等等。

    为了使代码对使用它的程序员更明显,您应该考虑使用一个临时值, use 而不是拥有资源的对象,并从一个方法名返回它,该方法名告诉程序员它在做什么,即临时获取一些资源。

    让我举个例子。

    而不是这个:

    using (node) { ... }
    

    您可以这样做:

    using (node.ResourceScope()) { ... }
    

    因此,你不会再处理任何东西了,因为 ResourceScope 将返回您丢弃的新值,基础节点将保持原样。

    下面是一个示例实现(未验证,从内存键入):

    public class Node
    {
        private Resource _Resource;
    
        public void AcquireResource()
        {
            if (_Resource == null)
                _Resource = InternalAcquireResource();
        }
    
        public void ReleaseResource()
        {
            if (_Resource != null)
            {
                InternalReleaseResource();
                _Resource = null;
            }
        }
    
        public ResourceScopeValue ResourceScope()
        {
            if (_Resource == null)
                return new ResourceScopeValue(this);
            else
                return new ResourceScopeValue(null);
        }
    
        public struct ResourceScopeValue : IDisposable
        {
            private Node _Node;
    
            internal ResourceScopeValue(Node node)
            {
                _Node = node;
                if (node != null)
                    node.AcquireResource();
            }
    
            public void Dispose()
            {
                Node node = _Node;
                _Node = null;
                if (node != null)
                    node.ReleaseResource();
            }
        }
    }
    

    这允许您执行以下操作:

    Node node = ...
    using (node.ResourceScope())     // first call, acquire resource
    {
        CallSomeMethod(node);
    }                                // and release it here
    
    ...
    private void CallSomeMethod(Node node)
    {
        using (node.ResourceScope()) // due to the code, resources will not be 2x acquired
        {
        }                            // nor released here
    }
    

    我返回一个结构,而不是 IDisposable 意味着您将不会得到装箱开销,而只在从中退出时调用public.Dispose方法。 使用 -块。

        2
  •  5
  •   Jon Skeet    15 年前

    这还不完全清楚 IDisposable.Dispose 文档,其中包括(强调我的):

    执行与释放、释放、 或重置 非托管资源。

    但这也是:

    如果对象的Dispose方法为 多次调用,对象必须 忽略第一个调用之后的所有调用。 对象不能引发异常 如果调用了其Dispose方法 多次。实例方法其他 比Dispose可以抛出 当资源 已释放。

    后者表明 不应该 用于“重置”类型的操作,这是我认为你需要的。(我不确定您的“进入睡眠”术语是否真的有帮助;我说的对吗?您是否真的在处理所有子节点中的所有主动获得的资源?)

    当然,这只是惯例-你 可以 让你的代码随心所欲。但是,我认为大多数开发人员会觉得这有点奇怪。

    我知道你想做什么,但我不确定最好的方法…

        3
  •  2
  •   John Saunders    15 年前

    我会说,“不”。 IDisposable 有一个特殊的目的,而“睡觉”不是吗?

        4
  •  1
  •   User Friendly    15 年前

    当然,如果没有资源,就不应该释放它们,因此在这种情况下,Dispose方法将不做任何事情。

    也许,您应该使用一个内部具有IDisposable的复合对象,并在此属性/字段中分配/释放资源。因此,当节点处于活动状态时,您将唤醒(使用资源分配新对象)并进入睡眠状态(释放资源)。

    在这种情况下,Steel需要从IDisposable派生您的节点,因为当您具有IDisposable的属性/字段时,容器也应该实现IDisposable。

        5
  •  1
  •   Chris Shaffer    15 年前

    不,这似乎不适合使用IDisposable。

    快速考虑您可以做什么;实现另一个IDisposable对象,该对象可以包含加载的数据,并从对象的方法调用返回该数据;例如:

    using(var wokenUpData = dataContainer.WakeUp())
    {
        // access the data using wokenUpData
        ...
    }
    
        6
  •  1
  •   philsquared    15 年前

    听起来你只需要再加一个 level of indirection .

    这里混淆的是物体的生命周期。一方面,你有一个长寿的物体( node ,这并不总是使用其他资源。另一方面,你有那些其他的资源,一个节点可以在“唤醒”时使用,并且在回到睡眠状态(由于选择了另一个节点)时会放弃(如果我让你正确的话)。

    因此,听起来您有两个生命周期概念,可以通过引入另一个对象来更直接地管理资源来建模这些概念。

    所以,把你的“懒惰获取的资源”放进新的资源管理对象中,这个对象本身就热切地获取资源,并将它们配置到 dispose() . 然后,您的节点就可以根据需要(在唤醒时)创建资源管理对象,并在完成时(返回睡眠状态)对其进行处理,并且生命周期不会混淆。