C#:using block:对象重新初始化

在“使用”块内重新初始化是一个坏主意,要始终避免。 我还是会问这个:

为什么“使用”调用处理原始值而不是最后一个引用或重新初始化(如果使用try finally block则会发生这种情况)

MyClass b = new MyClass();// implements Idisposable MyClass c = new MyClass(); MyClass a ; using (a = new MyClass()) { a = b; a = c; } 

在上面的代码中,dispose将在原始引用上调用,而不是在引用时更新。 这可以通过在dispose方法中在控制台上打印一些内容来轻松validation。

但是,使用try {} finally代码调用最后一个引用dispose方法。

 try { a = new MyClass(); a = b; a = c; } finally { a.Dispose(); } 

MSDN :using语句确保即使在对象上调用方法时发生exception,也会调用Dispose。

 using (Font font1 = new Font("Arial", 10.0f)) { byte charset = font1.GdiCharSet; } 

基本上“使用”转换为:

 { Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } } 

编译器生成以下代码:

 MyClass b = new MyClass(); MyClass a; MyClass cs$3$000 = a = new MyClass(); try { a = b; } finally { if (cs$3$000 != null) cs$3$000.Dispose(); } 

自动生成的cs $ 3 $ 000局部变量实现了合同。

C#规范中定义了两种using语句forms:

 using-statement: using ( resource-acquisition ) embedded-statement resource-acquisition: local-variable-declaration expression 

如果您有local-variable-declaration ,则不会有任何问题。 该变量在使用块中是只读的,您根本无法更改它。 规范说:

8.13使用声明

[…]在任一扩展中,资源变量在嵌入语句中是只读的。

在这里,我们正在处理第二种forms: resource-acquisitionexpression而不是local-variable-declaration 。 在这种情况下,C#规范清楚地说:

表格的使用声明

  using (expression) statement 

具有相同的两个可能的扩展,但在这种情况下,ResourceType隐式地是表达式的编译时类型,并且资源变量在嵌入语句中是不可访问的,并且对于嵌入语句是不可见的[强调我的]

显然,您无法更改不可见的,无法访问的变量。 其值仅在使用resource-acquisition子句中分配。 因此,它将具有变量的旧值,而不是新值。

当您处理已声明变量的赋值时,您正在使用using语句的这种forms。 事实上,您正在为变量赋值

 using ( x = something ) 

是无关紧要的。 整个x = something被视为一个表达式,只有该表达式的值才是重要的。 重要的是要知道“资源变量”在这里不是“x”。 这是一个看不见的变量。 从编译器的角度来看,以下构造之间没有太大区别:

 using ( something ) using ( x = something ) using ( y = x = something ) 

在所有情况下,表达式都将被执行, 将被保证处理,而不是变量。 如果这不是定义的行为并且您在上面的块中编写了第三行,编译器应该怎么做? 配置xy ? 都? 都不是? 目前的行为是有道理的。

使用可以看作是对使用声明声明的对象进行调用的承诺。 恕我直言,这是唯一有意义的事情!

如果您在重新分配的值上调用dispose,则不会处理原始值。

似乎“使用”是创建它自己的变量来存储引用,它被初始化为“a”,而“a”又被初始化为对象的第一个实例。 稍后当您更改“a”时,您并未真正更改存储在“using”语句中的原始引用。 这似乎是一个非常好的function,因为using负责处理using语句中提到的实际对象,而不是变量。

是的,这很有趣。

所以我查看了反编译的代码:

  IL_0000: nop IL_0001: newobj instance void ConsoleApplication17.Foo1::.ctor() IL_0006: dup IL_0007: stloc.0 IL_0008: stloc.1 // 1. note this .try { IL_0009: nop IL_000a: newobj instance void ConsoleApplication17.Foo2::.ctor() IL_000f: stloc.0 // 2. and this IL_0010: nop IL_0011: leave.s IL_0023 } // end .try finally { IL_0013: ldloc.1 // 3. and this IL_0014: ldnull IL_0015: ceq IL_0017: stloc.2 IL_0018: ldloc.2 IL_0019: brtrue.s IL_0022 IL_001b: ldloc.1 IL_001c: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0021: nop IL_0022: endfinally } // end handler IL_0023: nop 

因此它确实复制了引用,并在以后使用它的副本,允许您在终结器方面重置变量,而不是真正实现任何目标。

真的,如果你只能在using语句中使用’只读’变量那就太好了。 这有点令人困惑。 当然,MSDN具有误导性。

将在using子句的参数中引用的对象上调用Dispose。 就这么简单。