为什么处置对象在处置后不会抛出异常?

时间:2011-09-17 18:33:32

标签: c# dispose idisposable

在被处置对象上调用方法是否合法?如果是,为什么?

在下面的演示程序中,我有一个一次性类A(实现IDisposable接口)。据我所知,如果我将一次性对象传递给using()构造,然后在结束括号中自动调用Dispose()方法:

A a = new A();
using (a)
{
   //...
}//<--------- a.Dispose() gets called here!

//here the object is supposed to be disposed, 
//and shouldn't be used, as far as I understand.

如果这是正确的,请解释该程序的输出:

public class A : IDisposable
{
   int i = 100;
   public void Dispose()
   {
      Console.WriteLine("Dispose() called");
   }
   public void f()
   {
      Console.WriteLine("{0}", i); i  *= 2;
   }
}

public class Test
{
        public static void Main()
        {
                A a = new A();
                Console.WriteLine("Before using()");
                a.f();
                using ( a) 
                {
                    Console.WriteLine("Inside using()");
                    a.f();
                }
                Console.WriteLine("After using()");
                a.f();
        }
}

输出(ideone):

Before using()
100
Inside using()
200
Dispose() called
After using()
400

如何在已处置对象f()上调用a?这是允许的吗?如果是,那为什么呢?如果不是,为什么上述程序在运行时不会出现异常?


我知道使用using的流行结构是:

using (A a = new A())
{
   //working with a
}

但我只是在尝试,这就是我写不同的原因。

6 个答案:

答案 0 :(得分:13)

处置并不意味着消失。仅处理意味着已释放任何非托管资源(如文件,任何类型的连接......)。虽然这通常意味着该对象不提供任何有用的功能,但可能仍有一些方法不依赖于该非托管资源并仍然照常工作。

Disposing机制存在,因为.net(并且继承,C#.net)是一个垃圾收集环境,这意味着您不负责内存管理。但是,垃圾收集器无法确定是否已完成非托管资源的使用,因此您需要自己执行此操作。

如果希望方法在对象被放置后抛出异常,则需要一个布尔值来捕获dispose状态,一旦对象被释放,就抛出异常:

public class A : IDisposable
{
   int i = 100;
   bool disposed = false;
   public void Dispose()
   {
      disposed = true;
      Console.WriteLine("Dispose() called");
   }
   public void f()
   {
      if(disposed)
        throw new ObjectDisposedException();

      Console.WriteLine("{0}", i); i  *= 2;
   }
}

答案 1 :(得分:6)

不会抛出异常,因为在调用ObjectDisposedException之后,您没有设计方法抛出Dispose

一旦调用Dispose,clr就不会自动知道它应该抛出ObjectDisposedException。如果Dispose已经发布了成功执行方法所需的任何资源,那么您有责任抛出异常。

答案 2 :(得分:4)

典型的Dispose()实现仅在它存储在一次性字段中的任何对象上调用Dispose()。这又反过来释放了非托管资源。如果您实现IDisposable并且实际上没有做任何事情,就像您在代码片段中所做的那样,那么对象状态根本不会改变。什么都不会出错。不要混淆处理和最终确定。

答案 3 :(得分:3)

IDisposable的目的是允许一个对象修复任何外部实体的状态,这些外部实体为了它的利益而被置于一个不太理想的状态用于其他目的。例如,Io.Ports.SerialPort对象可能已将串行端口的状态从“可用于任何需要它的应用程序”更改为“仅可由一个特定的Io.Ports.SerialPort对象使用”; SerialPort.Dispose的主要目的是将串行端口的状态恢复为“可用于任何应用程序”。

当然,一旦实现IDisposable的对象重置了为了其利益而维持某个状态的实体,它将不再具有这些实体维护状态的好处。例如,一旦串行端口的状态设置为“可用于任何应用程序”,与其关联的数据流就不能再用于发送和接收数据。如果一个对象可以正常运行而外部实体没有为了它的利益而被置于特殊状态,那么就没有理由首先将外部实体留在特殊状态。

通常,在对象上调用IDisposable.Dispose后,不应期望该对象能够做很多事情。试图在这样的对象上使用大多数方法会表明存在错误;如果一个方法无法合理地预期工作,那么表明这种方法的正确方法是通过ObjectDisposedException。

Microsoft建议,实现IDisposable的对象上的几乎所有方法都应抛出ObjectDisposedException,如果它们用于已经处置的对象上。我建议这样的建议过于宽泛。设备通常非常有用,可以公开方法或属性,以找出对象存活时发生的事情。虽然可以给一个通信类一个Close方法和一个Dispose方法,并且只允许一个人在关闭后查询像NumberOfPacketsExchanged这样的东西,但是在Dispose之后,但这看起来过于复杂。读取与在Disposed之前发生的事情相关的属性似乎是一种非常合理的模式。

答案 4 :(得分:2)

C#中的处理程序与C ++中的析构函数不同。处理程序用于在对象保持有效时释放托管(或非托管)资源。

根据类的实现而抛出异常。如果f()不需要使用已经处置的对象,那么它不一定需要抛出异常。

答案 5 :(得分:2)

调用Dispose()不会将对象引用设置为null,并且如果在Dispose()之后访问其函数,则自定义一次性类不包含任何抛出异常的逻辑被称为所以它当然是合法的。

在现实世界中,Dispose()释放非托管资源,此后这些资源将无法使用,和/或如果您在调用{{1}后尝试使用该对象,则类作者将其抛出ObjectDisposedException }}。通常,类级别的布尔值将在Dispose()的主体内设置为true,并且在执行任何工作之前在类的其他成员中检查该值,如果bool为true,则抛出异常。