【问题标题】:Curious C# using statement expansion使用语句扩展的好奇 C#
【发布时间】:2009-06-03 20:23:00
【问题描述】:

我已经运行 ildasm 发现这个:

    using(Simple simp = new Simple())
    {
        Console.WriteLine("here");
    }

生成与此等效的 IL 代码:

    Simple simp = new Simple();
    try
    {
        Console.WriteLine("here");
    }
    finally
    {
        if(simp != null)
        {
            simp.Dispose();
        }
    }

问题是为什么它会在 finally 中检查 null ? finally 块只有在 try 块被执行时才会被执行,而 try 块只有在 Simple 构造函数成功(即不抛出异常)时才会被执行,在这种情况下 simp 将是非空的。 (如果担心在 Simple 构造函数和 try 块的开头之间可能会出现一些干预步骤,那么这确实是一个问题,因为可能会引发异常,从而阻止 finally 块的执行。)那么,到底为什么?

抛开 using 语句是否比 try-finally 更好的论点,我将 try-finally 块写为:

    Simple simp = new Simple();
    try
    {
        Console.WriteLine("here");
    }
    finally
    {
        simp.Dispose();
        simp = null;        // sanity-check in case I touch simp again
                            // because I don't rely on all classes
                            // necessarily throwing
                            // ObjectDisposedException
    }

【问题讨论】:

  • 我对一件事感到好奇:就性能而言,与编译器生成的健全性检查相比,额外的健全性检查(simp = null)有多“昂贵”?最后,这两者之间的区别似乎更具哲学性而不是实际性,但我可能弄错了。无论哪种方式都很有趣。
  • @Fredrik - 所以你问的是“设置为空”是否比“比较空”更快/慢?我不知道。除此之外,using 语句的一个好处是您不必担心该对象在 using 范围之外被访问。 (除非你持有另一个对它的引用。)
  • "为什么它会在 finally 中检查 null?"没有很好的理由。跳过空值检查是我们可以执行的优化。我们没有。没什么大不了的;空检查既短又便宜。
  • 顺便说一句,如果一个表达式已知是非空的,因为它是新表达式的结果,C# 编译器会在许多地方执行类似这样的微优化。这只是我们错过的一个。
  • 我在这里简单讨论一下这个优化:blogs.msdn.com/ericlippert/archive/2009/06/11/…

标签: c# using finally expansion try-catch


【解决方案1】:

不,finally 块总是会被执行。您可能不是从新的对象获取对象,而是从返回对象的其他函数获取对象 - 它可能返回 NULL。 using() 是你的朋友!

dss539 好心地建议我附上他的笔记:

using(Simple simp = null) 

是扩展必须首先检查 null 的另一个原因。

【讨论】:

  • 只是为了扩展一点,您可能正在使用工厂模式并执行类似 using (myFactory.CreateMyObject()) 之类的操作,这可能会导致空对象。
  • 如果构造函数失败(在尝试之前),finally 将不会被执行。
  • 假设'new'如果失败会抛出异常,因此永远不会返回Null。如果它抛出异常,则甚至不会到达 try 块,并且 finally 块不会被执行。所以,finally 块并不总是被执行。
  • @spoulson & @Zifre:你是对的 - try {} 至少必须执行。但是 using() 是为一般情况设计的。它可能不是一个新的运算符 - 正如 Max 指出的那样,它可能是一个工厂或其他可能返回 null 的方法。如果失败,则不尝试 {}。如果成功或者返回null,就会执行try。
  • @n8 - 您应该将我的答案编辑到您的答案中,以便 OP 可以接受您的完整答案。
【解决方案2】:

using(Simple simp = null) 是扩展必须首先检查 null 的另一个原因。

【讨论】:

  • 只有使用 simp 对象才能保证是运行时错误。当然,如果你不这样做,就没有理由使用......
  • 虽然你不太可能明确编码 = null,但它可能是 = SomeFactoryMethod(),它可以返回 null。
  • 使用 (Simple simp = null) { if (someCondition) simp = new Simple(1); else if (otherCondition) simp = new Simple(2); ... } 会在 simp 存在的任何情况下处理它。
  • @configurator - 是的,但这仍然有点……危险。 simp = new Simple();然后另一个 simp = new Simple();它只会处理其中一个
  • ...它无法编译:错误 CS1656:无法分配给“simp”,因为它是“使用变量”
【解决方案3】:

MSDN 在 using 语句中。

我认为奇怪的是它没有扩展到:

Simple simp = new Simple();
Simple __compilergeneratedtmpname = simp;
try
{
    Console.WriteLine("here");
}
finally
{
    if(__compilergeneratedtmpname != null)
    {
        __compilergeneratedtmpname.Dispose();
    }
}

【讨论】:

  • 您希望能够在 using 块中更改 simp 变量??
  • 我没有意识到编译器将 simp 强制为有效只读。
  • Dolphin:多线程时会出现问题。当构造函数紧跟在 try 之后时,CLR 保证没有线程会在中间“中断”。你的方式,没有保证。
  • @configurator:没想到!
【解决方案4】:

看来您的评论:

“如果担心在 Simple 构造函数和 try 块的开头之间可能会出现一些干预步骤,那么这确实是一个问题,因为可能会引发异常,从而阻止 finally 块在全部。”

可能已经死了。见:

Atomicity & Asynchronous Exception Failures

我还想指出 WCF 和使用的问题:

Avoiding Problems with the Using Statement and WCF Service Proxies 引用:

Avoiding Problems with the Using Statement

【讨论】:

    【解决方案5】:

    代码必须以这种方式翻译以避免在处理对象时可能出现的NullReferenceException。根据C# 语言参考using statement 不仅接受局部变量声明作为其第一个非终结符resource_acquisition 符号,还接受任何表达式。考虑以下代码:

    DisposableType @object = null;
    using(@object) {
        // whatever
    }
    

    显然,除非finnaly 块中的空条件@object?.Dispose(),否则会发生异常。仅当 expression 是不可为空的值类型(不可为空的结构)时,空检查才是多余的。事实上,根据上述语言参考,在这种情况下它是不存在的。

    【讨论】:

      猜你喜欢
      • 2017-12-24
      • 1970-01-01
      • 2016-03-02
      • 1970-01-01
      • 1970-01-01
      • 2011-03-26
      • 2023-01-28
      • 2021-09-05
      • 2022-10-17
      相关资源
      最近更新 更多