好奇的C#使用语句展开

好奇的C#使用语句展开,c#,using,finally,expansion,try-catch,C#,Using,Finally,Expansion,Try Catch,我运行ildasm发现: using(Simple simp = new Simple()) { Console.WriteLine("here"); } 生成与此等效的IL代码: Simple simp = new Simple(); try { Console.WriteLine("here"); } finally { if(simp != null)

我运行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?只有在执行try块时,才会执行finally块,只有在简单构造函数成功(即未引发异常)时,才会执行try块,在这种情况下,simp将为非null。(如果有人担心简单构造函数和try块的开头之间可能会出现一些中间步骤,那么这确实是个问题,因为可能会抛出一个异常,从而阻止finally块执行。)那么,为什么呢

抛开(请)关于using语句是否优于try FILLATION语句的争论,我将try FILLATION块写为:

    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
    }

否,将始终执行finally块。您可能不是从新函数获取对象,而是从返回对象的其他函数获取对象,并且它可能返回NULL。使用()是你的朋友

dss539好心地建议我将他的笔记包括在内:

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

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

关于using语句

我觉得奇怪的是,它没有扩展到:

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

您的评论似乎是:

如果有人担心简单构造函数和try块的开头之间可能会出现一些中间步骤,那么这将是一个真正的问题,因为可能会引发异常,从而阻止finally块执行

很可能是死定了。见:

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

其中提到:


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

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


显然,除非
finnaly
块中的null conditional
@object?.Dispose()
,否则会出现异常。只有当表达式是不可为空的值类型(不可为空的结构)时,空检查才是多余的。事实上,根据前面提到的语言引用,在这种情况下它是不存在的。

只是稍微扩展一下,您可以使用工厂模式并执行类似于使用(myFactory.CreateMyObject())的操作,这可能会导致空对象。但是如果构造函数失败(在尝试之前),finally将不会执行。假设“new”在失败时将抛出异常,因此永远不会返回Null。如果抛出异常,try块甚至不会到达,finally块也不会执行。因此,finally块并不总是被执行。@spolson&@Zifre:你说得对-try{}至少必须执行。但是using()是为一般情况而设计的。它可能不是一个新的操作符——正如Max指出的,它可能是一个工厂或其他可能返回null的方法。如果失败,请不要尝试{}。如果成功或返回null,将执行try。@n8-您应该将我的答案编辑到您的答案中,以便OP可以将您的答案作为完整答案接受。只有在使用simp对象时,才能保证这是一个运行时错误。如果您不这样做的话,就没有理由使用它了。虽然您不太可能明确地编码=null,但它可以是=SomeFactoryMethod(),它可以返回null.using(Simple simp=null){if(someCondition)simp=new Simple(1);else if(otherCondition)simp=new Simple(2);…}在任何情况下都会处理simp。@configurator-是,但这仍然有点。。。危险的simp=newsimple();然后是另一个simp=newsimple();它只会处理其中一个…并且它不会编译:错误CS1656:无法分配给“simp”,因为它是一个“使用变量”。我很好奇一件事:与编译器生成的健全性检查相比,额外的健全性检查(simp=null)在性能上有多“昂贵”?归根结底,这两者之间的区别似乎更具哲理性而非实用性,但我可能弄错了。有趣的讨论。@Fredrik-那么你是在问“设置为空”是否比“比较为空”快/慢?我不确定。除此之外,使用语句的一个好处是您不必担心在使用范围之外访问该对象。(除非你持有另一个对它的引用。)“为什么它在finally中检查null?”没有很好的理由。跳过空检查是我们本可以执行的优化。我们没有。没什么大不了的;空检查短且便宜。顺便说一句,如果一个表达式因为是新表达式的结果而被认为是非空的,那么C#编译器会在很多地方执行类似的微优化。这就是我们错过的一个。我在这里简要讨论了这个优化:您希望能够在using块中更改
simp
变量??我没有意识到编译器将simp强制为有效的只读。Dolphin:这会在多线程处理时引起问题。当构造函数紧跟在尝试之后,CLR保证没有线程会在中间中断。你的方式,没有保证。@configurator:我没意识到!