通过设置为null来处理?

5

我知道如何以传统方式处理IDisposable。比如,在Windows服务的OnStop()方法中关闭消息队列客户端:

        if (client != null)
        {
            client.Dispose();
        }

今天我第一次看到有人用这种方式来做:

        using (client)
        {
            client = null;
        }

他的“使用”过程到底发生了什么,或者他是否完全正确地处理了它?
5个回答

6
using(){}语句获取引用变量的副本,因此使用null进行赋值是无效的。

但也是无害的(它不会干扰调用dispose的操作) - Martijn
在第二种情况下,同意您必须再次复制指针。 在Dispose内部隐式执行Dispose没有任何意义。 在任何情况下,当重新进入dispose时,您还需要进行空引用检查以避免空引用异常。 - George Mamaladze
1
@Martijn - 通常是无害的?但如果您想要推迟client的Dispose,那就不是了。虽然罕见,但有可能发生。 - H H
@Henk Holterman:并不是非常罕见——这种推迟在构造函数中是一个非常好的模式。 - supercat

4
你的同事使用using的代码可以工作,但可能有点过于复杂;
using(client) {
    client = null;
}

实质上,它是:

{ // scope here to denote that 'tmp' is not defined outside this scope
  var tmp = client;
  try {
      client = null;
  } finally {
      if(tmp != null) tmp.Dispose();
  }
}

并非所有情况都是如此,因为需要考虑值类型和显式接口实现。

个人而言,在可能的情况下(即在最初分配客户端的代码中),我会尽可能地使用using

我可能会在懒加载中使用这个方法:

using(client as IDisposable) { client = null; } // dispose if needed

例如,client 是我无法控制的某个东西,我不确定它是否实现了 IDisposable,但如果它确实实现了,我需要释放它。


顺便说一句,我认为可以增强“using”的可用性的一件事是能够显式地将“影子变量”设置为null,这样对象就不会被处理,或者有一个只在出现故障时才处理的“using”版本。例如,构造函数可以“using”要构造的对象的字段,然后在返回之前决定“保留”它们。可以使用try/finally块代替“using”,但它们更冗长,不够清晰。 - supercat
@supercat 我认为那种情况已经相当复杂了 - 最好明确地表达出来,并理解正在发生的事情(也就是说,我对 try/finally 感到满意)。 - Marc Gravell
我刚刚再次查看了代码,认为我已经弄清楚了它的目的——如果“client”是一个字段或属性,则其生命周期可能无法限制在“using”块中。在某些情况下,在调用Dispose之前,有必要清除保存对该对象引用的字段或属性。这里显示的“using”代码比显式创建临时变量更简洁,但更好的方法是使用“Zap”例程来实现此目的。 - supercat

3

退出

using (client)
{
}

client.Dispose() 会自动为您调用。
client = null 应该在代码之外被调用,以我的意见。
请记住,要使用 using(object),对象应该实现 IDisposable 接口。


2
为什么要踩我?如果我说错了什么,请解释一下,这样我就可以更正或删除我的回答... - Marco
不,没有什么特别的问题;只是有一个用户投了几个奇怪的反对票;与此帖子中涉及的任何用户没有强烈的联系。只是...很奇怪,真的。 - Marc Gravell

0
在我看来,这并不是有效的。由于客户端被设置为null,使用using就没有任何引用需要处理了,即使实际对象仍然在内存中,但不再被任何变量引用(它将稍后被垃圾回收,但使用的意义何在?)。

0
如果“client”的范围超出“using”块,那么代码将确保在控制离开“using”块之前处理“client”,并且对新的已失效的“client”对象的引用将被销毁。如果它是一个字段,则将其引用设置为null可能很重要,如果它是附加事件的属性,则可能非常关键。
我猜“client”是一个字段或属性,并且“using”代码的作者认为它比Marc Gravell给出的版本更简洁。我会承认作者的代码很简洁,但我建议更简洁和更清晰的方法是定义一个通用的“Zap”例程,该例程通过引用接受一个IDisposable,使用Interlocked.Exchange读取并将其设置为null,如果它不是null,则处理它。在这种情况下,“using”语句将被替换为:
  Zap(ref client);
这样做的好处是更简洁,同时几乎肯定更清晰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接