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

谁处置可识别的公共财产?

  •  28
  • GrahamS  · 技术社区  · 17 年前

    SomeDisposableObject IDisposable :

    class SomeDisposableObject : IDisposable
    {
        public void Dispose()
        {
            // Do some important disposal work.
        }
    }
    

    AContainer ,其中有一个 可处置的物体 作为公共财产:

    class AContainer
    {
        SomeDisposableObject m_someObject = new SomeDisposableObject();
    
        public SomeDisposableObject SomeObject
        {
            get { return m_someObject; }
            set { m_someObject = value; }
        }
    }
    

    那么FxCop会坚持 可识别

    这很好,但我不知道如何安全地打电话 m_someObject.Dispose() AContainer.Dispose() m_someObject 例子

    避免这种情况的最佳方法是什么?

    (假设其他代码依赖于 AContainer.SomeObject 容器

    :我将用一些例子展开讨论,因为我认为一些评论者忽略了这个问题。如果我只是实现一个 Dispose() 方法论 容器 调用m_someObject.Dispose(),然后剩下以下情况:

    // Example One
    AContainer container1 = new AContainer();
    SomeDisposableObject obj1 = container1.SomeObject;
    container1.Dispose();
    obj1.DoSomething(); // BAD because obj1 has been disposed by container1.
    
    // Example Two
    AContainer container2 = new AContainer();
    SomeObject obj2 = new SomeObject();
    container2.SomeObject = obj2; // BAD because the previous value of SomeObject not disposed.
    container2.Dispose();
    obj2.DoSomething(); // BAD because obj2 has been disposed by container2, which doesn't really "own" it anyway.  
    

    这有用吗?

    10 回复  |  直到 17 年前
        1
  •  25
  •   Community Mohan Dere    9 年前

    没有单一的答案,这取决于您的场景,关键点是由属性表示的可支配资源的所有权,如图所示 Jon Skeet points out .

    • . System.IO.StreamReader公开一次性属性BaseStream。它被认为拥有底层流,而处理StreamReader总是处理底层流。

    • 容器从不处理

      在这种情况下,每次取消引用父属性时都会返回一个新的DirectoryEntry实例,调用方可能会处理它。可以说,这违反了属性的指导原则,也许应该有一个GetParent()方法。

    • 容器有时会被丢弃 . System.Data.SqlClient.SqlDataReader公开一个一次性连接属性,但调用方使用SqlCommand.ExecuteReader的CommandBehavior参数来决定读取器是否拥有(并因此处置)基础连接。

    另一个有趣的例子是System.DirectoryServices.DirectorySearcher,它有一个读/写一次性属性SearchRoot。如果此属性是从外部设置的,则假定基础资源不属于容器,因此不由容器处理。如果不是从外部设置的,则会在内部生成一个引用,并设置一个标志以确保将对其进行处理。你可以通过Lutz反射器看到这一点。

    您需要确定容器是否拥有资源,并确保准确记录其行为。

    public SomeDisposableObject SomeObject    
    {        
        get { return m_someObject; }        
        set 
        { 
            if ((m_someObject != null) && 
                (!object.ReferenceEquals(m_someObject, value))
            {
                m_someObject.Dispose();
            }
            m_someObject = value; 
        }    
    }
    private SomeDisposableObject m_someObject;
    

    使现代化

        2
  •  15
  •   Jon Skeet    17 年前

    这实际上取决于谁名义上“拥有”一次性物品。在某些情况下,您可能希望能够传入对象,例如在构造函数中,而不需要您的类负责清理它。其他时候你可能想自己清理一下。如果您正在创建对象(如在示例代码中),那么几乎可以肯定,清理对象是您的责任。

    至于财产——我不认为拥有财产真的应该转让所有权或诸如此类的东西。如果您的类型负责处理该对象,则应保留该责任。

        3
  •  5
  •   Igor Zelaya    17 年前

    真正的问题可能是面向对象的设计。如果容器已被处置,则其所有成员对象也应被处置。如果不是这样的话,听起来你可以处理一个身体,但你想让腿实例活下来。听起来不对。

        4
  •  4
  •   Armin Ronacher    17 年前

    IDisposable 用一个 Dispose 处理包装好的一次性物品的方法。现在调用代码必须确保 using() 是否使用或是否使用了等效物 try finally 处理对象的代码。

        5
  •  3
  •   GrahamS    17 年前

    首先要避免它


    有两种明显的方法可以做到这一点。


    如果 AContainer 不会创建一个 SomeDisposableObject 容器 将不再“拥有”该实例,并且不负责处理该实例。

    public class AContainerClass
    {
        SomeDisposableObject m_someObject; // No creation here.
    
        public AContainerClass(SomeDisposableObject someObject)
        {
            m_someObject = someObject;
        }
    
        public SomeDisposableObject SomeObject
        {
            get { return m_someObject; }
            set { m_someObject = value; }
        }
    }
    

    保持实例的私密性
    发布代码的主要问题是所有权混淆。当时 类无法确定实例的所有者。它可以是它创建的实例,也可以是外部和外部创建的其他实例 set 通过物业。

    即使它跟踪这一点,并且确定它正在处理它创建的实例,那么它仍然不能 处置它,因为其他类别现在可能有从公共财产获得的对它的引用。

    如果无法避免的话。。。

    如果出于某种原因,代码无法以这些方式重构(正如我在问题中所规定的),那么我认为您将面临一些相当困难的设计选择。


    如果您选择这种方法,那么您实际上是在声明 容器 将拥有 可处置的物体

    这在某些情况下是有意义的,特别是在 显然是一个暂时的或从属的对象。但是,应该仔细记录它,因为它要求调用代码知道这种所有权转移。

    (使用方法而不是属性可能更合适,因为方法名称可以用来进一步提示所有权)。

    public class AContainerClass: IDisposable
    {
        SomeDisposableObject m_someObject = new SomeDisposableObject();
    
        public SomeDisposableObject SomeObject
        {
            get { return m_someObject; }
            set 
            {
                if (m_someObject != null && m_someObject != value)
                    m_someObject.Dispose();
    
                m_someObject = value;
            }
        }
    
        public void Dispose()
        {
            if (m_someObject != null)
                m_someObject.Dispose();
    
            GC.SuppressFinalize(this);
        }
    }
    

    仅当仍为原始实例时才进行处置
    在这种方法中,您将跟踪实例是否已从最初由创建的实例更改 容器 只有当它是原件时才处理。这里的所有权模式是混合的。 容器 实例,但如果提供了外部实例,则外部代码仍有责任处理该实例。

    此方法最能反映此处的实际情况,但可能难以正确实施。客户端代码仍然可以通过执行以下操作导致问题:

    AContainerClass aContainer = new AContainerClass();
    SomeDisposableObject originalInstance = aContainer.SomeObject;
    aContainer.SomeObject = new SomeDisposableObject();
    aContainer.DoSomething();
    aContainer.SomeObject = originalInstance;
    

    这里交换了一个新实例,调用了一个方法,然后恢复了原始实例。不幸的是 容器 我会打电话的 Dispose() 在原始实例上,当它被替换时,因此它现在无效。


    这显然不太理想。如果 可处置的物体 类确实包含一些稀缺资源,如果不及时处理它,肯定会给您带来问题。

    然而,就客户机代码如何与客户机交互而言,它也可能是最健壮的方法 容器 容器 处理文件的所有权 可处置的物体 例子


    一些评论者建议,如果有任何其他类仍然引用 可处置的物体

        6
  •  2
  •   Brian Rasmussen    17 年前

    你不能安全地打电话的原因 Dispose() 在…上 AContainer 的实例 SomeDisposableObject

    问题类似于允许访问用于锁定的实例。如果这样做,就很难确定从何处获取锁。

    如果您可以避免暴露您的一次性实例,那么谁将处理对您的调用这一问题 也会消失。

        7
  •  1
  •   Community Mohan Dere    9 年前

    我遇到的一件有趣的事情是,SqlCommand通常拥有一个SqlConnection(两者都实现IDisposable)实例。但是,在SqlCommand上调用dispose将 不

    在Stackoverflow的帮助下,我也发现了这一点 right here .

    因此,换句话说,“子”(嵌套?)实例是否可以/将在以后重用很重要。

        8
  •  0
  •   Mark Brackett    17 年前

    如果出于某种原因,您认为某个可处置对象应该比容器寿命更长,我只能想到以下方法:

    • 为SomeDisposableObject提供对容器的引用(请参阅WinForms控件和父属性)。只要某个可处理的对象是可访问的,那么容器也是可访问的。这将阻止GC处理容器,但是如果有人手动调用Dispose,那么您将处理一些DisposableObject。我想这是意料之中的。

    总而言之,我不确定这个设计是否合理。毕竟,您似乎期望客户端代码如下:

    SomeDisposableObject d;
    using (var c = new AContainer()) {
       d = c.SomeObject;
    }
    // do something with d
    

    对我来说,这似乎是坏掉的客户端代码。这违反了得墨忒尔的法律,对我来说是常识。

        9
  •  0
  •   mamu    17 年前

        10
  •  -1
  •   Program.X    17 年前

    您只需在Dispose()中标记处置。毕竟处置不是析构函数-对象仍然存在。

    class AContainer : IDisposable
    {
        bool _isDisposed=false;
    
        public void Dispose()
        {
            if (!_isDisposed) 
            {
               // dispose
            }
            _isDisposed=true;
        }
    }
    

    也将此添加到其他类中。

    推荐文章