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

你如何防止IDisposable传播到你所有的班级?

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

    从这些简单的类开始。。。

    class Bus
    {
        Driver busDriver = new Driver();
    }
    
    class Driver
    {
        Shoe[] shoes = { new Shoe(), new Shoe() };
    }
    
    class Shoe
    {
        Shoelace lace = new Shoelace();
    }
    
    class Shoelace
    {
        bool tied = false;
    }
    

    A. Bus 有一个 Driver 这个 驾驶员 有两个 Shoe s、 各 鞋 有一个 Shoelace . 都很傻。

    将IDisposable对象添加到鞋带

    鞋带 可以是多线程的,所以我添加了 EventWaitHandle 用于与之通信的线程。所以 现在看起来是这样的:

    class Shoelace
    {
        private AutoResetEvent waitHandle = new AutoResetEvent(false);
        bool tied = false;
        // ... other stuff ..
    }
    

    Microsoft's FxCop 将投诉: “在‘鞋带’上实现IDisposable,因为它创建以下IDisposable类型的成员:'EventWaitHandle'。”

    IDisposable 在…上

    class Shoelace : IDisposable
    {
        private AutoResetEvent waitHandle = new AutoResetEvent(false);
        bool tied = false;
        private bool disposed = false;
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        ~Shoelace()
        {
            Dispose(false);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (waitHandle != null)
                    {
                        waitHandle.Close();
                        waitHandle = null;
                    }
                }
                // No unmanaged resources to release otherwise they'd go here.
            }
            disposed = true;
        }
    }
    

    或者(正如评论人士所指出的)因为 鞋带 它本身没有非托管资源,我可以使用更简单的dispose实现,而不需要 Dispose(bool) 和析构函数:

    class Shoelace : IDisposable
    {
        private AutoResetEvent waitHandle = new AutoResetEvent(false);
        bool tied = false;
    
        public void Dispose()
        {
            if (waitHandle != null)
            {
                waitHandle.Close();
                waitHandle = null;
            }
            GC.SuppressFinalize(this);
        }
    }
    

    对,就是这样。但现在FxCop会抱怨 创建一个 鞋带 所以 鞋 必须是 也

    和 驾驶员 创造 所以 必须是 可识别 . 和 公共汽车 创造 驾驶员 所以 公共汽车 必须是 可识别 等等

    突然,我的零钱换成了现金 鞋带 这给我带来了很多工作,我的老板想知道我为什么需要结账 改变 鞋带 .

    你如何防止这种疾病的传播 可识别 ,但仍要确保正确处置非托管对象?

    8 回复  |  直到 9 年前
        1
  •  36
  •   Grant BlahaErath    14 年前

    你无法真正“阻止”IDisposable的传播。有些类需要处理,例如 AutoResetEvent ,而最有效的方法是在 Dispose() 方法以避免终结器的开销。但是必须以某种方式调用此方法,就像在您的示例中一样,封装或包含IDisposable的类必须处理这些类,因此它们也必须是一次性的,等等。避免这种情况的唯一方法是:

    • 尽可能避免使用IDisposable类,在单个位置锁定或等待事件,在单个位置保留昂贵的资源,等等
    • 仅在需要时创建它们,并在(完成后)立即处置它们 using 模式)

    在某些情况下,IDisposable可以忽略,因为它支持可选的情况。例如,WaitHandle实现IDisposable以支持命名互斥。如果未使用名称,Dispose方法将不执行任何操作。MemoryStream是另一个例子,它不使用系统资源,其Dispose实现也不做任何事情。仔细考虑是否正在使用非托管资源具有指导意义。检查.net库的可用源代码或使用反编译器也是如此。

        2
  •  20
  •   Bridge user2173966    12 年前

    您可以做的是避免向对象层次结构中的叶类添加IDisposable。这并不总是一项容易的任务,但却是一项有趣的练习。从逻辑的角度来看,没有理由说鞋带是一次性的。除了在这里添加WaitHandle,还可以在鞋带和WaitHandle的使用点添加关联。最简单的方法是通过字典实例。

    如果您可以通过实际使用WaitHandle的点处的映射将WaitHandle移动到松散关联中,则可以断开此链。

        3
  •  16
  •   Jordão    16 年前

    阻止 IDisposable 为了避免传播,您应该尝试将一次性对象的使用封装在单个方法中。尝试设计 Shoelace 不同的是:

    class Shoelace { 
      bool tied = false; 
    
      public void Tie() {
    
        using (var waitHandle = new AutoResetEvent(false)) {
    
          // you can even pass the disposable to other methods
          OtherMethod(waitHandle);
    
          // or hold it in a field (but FxCop will complain that your class is not disposable),
          // as long as you take control of its lifecycle
          _waitHandle = waitHandle;
          OtherMethodThatUsesTheWaitHandleFromTheField();
    
        } 
    
      }
    } 
    

    等待句柄的范围仅限于 Tie

    因为等待句柄是 鞋带 ,它不应该以任何方式更改其公共接口,比如在其声明中添加新接口。当你不再需要一次性字段时会发生什么,你会移除 可识别 鞋带 抽象 可识别 . 可识别 应该为抽象封装了需要确定性清理的资源的类保留;i、 例如,适用于可处置性属于

        4
  •  3
  •   Henk Holterman    17 年前

    这基本上就是将组合或聚合与一次性类混合时发生的情况。如前所述,第一种解决方法是从鞋带中重构waitHandle。

    话虽如此,在没有非托管资源的情况下,您可以大大减少一次性模式。(我仍在寻找这方面的官方参考。)

    但是您可以省略析构函数和GC.SuppressFinalize(这个);也许可以稍微清理一下虚拟的void Dispose(bool disposing)。

        5
  •  3
  •   GrahamS    17 年前

    有趣的是如果 Driver

    class Driver
    {
        Shoe[] shoes = { new Shoe(), new Shoe() };
    }
    

    那么什么时候 Shoe 制造 IDisposable ,FxCop(v1.36)没有抱怨 驾驶员 也应该是 可识别 .

    class Driver
    {
        Shoe leftShoe = new Shoe();
        Shoe rightShoe = new Shoe();
    }
    

    然后它会抱怨。

    我怀疑这只是FxCop的一个限制,而不是一个解决方案,因为在第一个版本中 鞋 实例仍在由 还需要以某种方式处理。

        6
  •  3
  •   Joh    16 年前

    我不认为有一种技术方法可以阻止IDisposable的传播,如果你让你的设计紧密结合的话。人们应该怀疑这个设计是否正确。

    另一种设计可以是:

    class Bus
    {
       IDriver busDriver = null;
       public void SetDriver(IDriver d) { busDriver = d; }
    }
    
    class Driver : IDriver
    {
       IShoePair shoes = null;
       public void PutShoesOn(IShoePair p) { shoes = p; }
    }
    
    class ShoePairWithDisposableLaces : IShoePair, IDisposable
    {
       Shoelace lace = new Shoelace();
    }
    
    class Shoelace : IDisposable
    {
       ...
    }
    

        7
  •  1
  •   Darragh    12 年前

    使用控制反转如何?

    class Bus
    {
        private Driver busDriver;
    
        public Bus(Driver busDriver)
        {
            this.busDriver = busDriver;
        }
    }
    
    class Driver
    {
        private Shoe[] shoes;
    
        public Driver(Shoe[] shoes)
        {
            this.shoes = shoes;
        }
    }
    
    class Shoe
    {
        private Shoelace lace;
    
        public Shoe(Shoelace lace)
        {
            this.lace = lace;
        }
    }
    
    class Shoelace
    {
        bool tied;
        private AutoResetEvent waitHandle;
    
        public Shoelace(bool tied, AutoResetEvent waitHandle)
        {
            this.tied = tied;
            this.waitHandle = waitHandle;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            using (var leftShoeWaitHandle = new AutoResetEvent(false))
            using (var rightShoeWaitHandle = new AutoResetEvent(false))
            {
                var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
            }
        }
    }
    
    推荐文章