C# 当对象设置为null时,目标为CLR的编译器能否生成Dispose方法调用?

C# 当对象设置为null时,目标为CLR的编译器能否生成Dispose方法调用?,c#,compiler-construction,dispose,C#,Compiler Construction,Dispose,当对象设置为null时,编译器(例如C#)能否自动生成对该对象的Dispose方法的调用(当然,对象首先应该支持Dispose方法)。例如,如果我们写 cnSqlConnection=null 而cnSqlConnection是SqlConnection类型的实例,C#编译器能否在将引用更新为null之前插入Dispose方法调用 此外,由于框架类确实支持Dispose方法可能被多次调用的场景,所以如果调用重复,则不会有任何伤害 (a)正确实现的对象应在其终结器中执行相同的清理逻辑。如果省略对D

当对象设置为null时,编译器(例如C#)能否自动生成对该对象的Dispose方法的调用(当然,对象首先应该支持Dispose方法)。例如,如果我们写

cnSqlConnection=null

而cnSqlConnection是SqlConnection类型的实例,C#编译器能否在将引用更新为null之前插入Dispose方法调用

此外,由于框架类确实支持Dispose方法可能被多次调用的场景,所以如果调用重复,则不会有任何伤害

(a)正确实现的对象应在其终结器中执行相同的清理逻辑。如果省略对
Dispose
的调用,那么终结器逻辑可能仍将运行

public static class MissingCompilerFeatures
{
    public static void SetToNullAndDispose(ref IDisposable obj)
    {
        if (obj != null) { obj.Dispose(); obj = null; }
    }
}

(b) 。总之:让人类程序员在正确的位置调用
Dispose
才是真正安全的。如果您将自动处理作为其逻辑结论,那么您将以引用计数结束,这是CLR内存模型所要避免的。

将对象引用设置为null将标记该对象为已释放以进行垃圾回收,但它不会显式调用Dispose。如果一个对象实现了IDisposable,那么在处理完该对象后,应该显式调用Dispose(),或者使用语句将其包装在

以下是一些关于IDisposable和垃圾收集的好信息:


从技术上讲,是的,编译器可以做到这一点,但理想情况下,它不会

原因是您在确定引用是否还有其他内容时会遇到问题。编译器无法推断谁在编译时持有引用(可能通过静态分析可以,但仍然不能保证)

现在,可以在运行时执行此操作,但这仍然不理想。每次将引用设置为null(标记和扫描)时,它都需要相当于一个GC。然后,对于任何GCed,如果有IDisposable实现,则调用Dispose。这会将CLR拖入泥潭,并使其表现糟糕

当然,总是有引用计数(正如nonnb在他对这个问题的评论中提到的),但这只是回到COM,而且也不理想。引用计数的复杂性首先导致了CLR的GC方面

需要考虑的事项:如果您不知道谁实际拥有IDisposable实现的所有权,那么这表示系统中存在设计缺陷。如果您有一个可以处理此类实例的函数,它应该明确声明它将处理此类实例,或者由调用方自行决定(后者是首选方法)。

是的,编译器可以自动生成
Dispose
调用。真正的问题是这是否是个好主意。这个问题的答案是绝对不

考虑下面的例子

void DoSomething(IDisposable disposable)
{
  DoSomethingElse(disposable);
  disposable = null;
}
在上面的示例中,如何确保
DoSomethingElse
不单独引用
disposable
实例,或者
DoSomething
的调用方做了类似的事情?如果在其他代码段假定对象是“活动”的情况下处理了该对象,那么这会把事情搞得一团糟

一个更有趣的问题是为什么
IDisposable
的结构在其作用域结束时会自动调用
Dispose
。显然,为什么对引用类型执行此操作不起作用,但结构呢?考虑以下并发症:

  • 你是否应该允许拳击
  • 是否应将它们限制为仅通过引用传递
  • 您应该允许对变量赋值吗
我确信这个列表并不详尽,但你明白了重点。这些问题是可以解决的,但真的值得努力吗?编译器可能不得不对它们施加太多的限制,以至于它们的用例变得非常有限。这是一个有趣的话题


关于这个问题,你可能会发现这很有趣。

这将是非常不一致的,我举一些例子:
给定类别:

class A : IDisposable { public void Dispose() { } }
例1:

IDisposable a = new A();
IDisposable b = a;
a = null; // The object is still alive in b, should it really be disposed?
例2:

IDisposable a = new A();
IDisposable b = new A();
a = b; // a is not accessible anymore, but not set to null, 
       //shouldn't it be disposed here?
例3:

private void Foo()
{
    IDisposable a = new A();
    // a is not used any more, but not set to null, 
    //shouldn't it be disposed here?
}
在c#中,有一个
using
块可以解决您所面临的问题:

using (IDisposable a = new A())
{
    // Do stuff
}   // Here a.Dispose() is automatically called.

它不能,它只是一个对象引用。同一个对象可能有许多引用,如果编译器自动执行此操作,它们都将指向一个死对象。那绝对是一个好机会

垃圾收集器的一个核心属性是,编译器和您都无法找出一个对象存在多少引用。只有垃圾收集器才能做到这一点。它已经完成了,如果所有引用都消失了,它会自动完成一个对象

IDisposable()的要点是不要等到这种情况发生。由于编译器无法做到这一点,也无法实现自动化,因此您必须保证不存在对该对象的其他实时引用。如果您不能做出这样的承诺,那么就不应该调用Dispose()并将其留给收集器。例如,COM互操作代码中的标准


其他自动内存管理方案可以做到这一点。它们使用引用计数,这在垃圾收集器成为主流之前很常见。昂贵且不可靠,因为它无法处理周期。微软资助了一项研究项目,将引用计数添加到CLR中。是的。GC在那之后得到了更多的尊重。

这听起来像是对VB中COM引用计数规则的一次回击。将引用设置为NULL、处理它和关闭连接都是不同的概念。@nonnb,当然它们在VB中也是不同的概念,只是混淆在一起,很容易通过循环引用泄漏内存……这个问题毫无意义,因为对象