关于using……的一些探讨
.NET中c#或者vb.net之所以那么吸引人,其重要原因之一就是其存在大量的“简化写法”——这些“简化写法”主要通过“语法糖”的形式存在(比如Lambda,匿名方法等……)。今天我们来探讨一下using……这个“语法糖”的本质和用法,以及一些常见的错误。
1)using……用法:
在C#或者是VB.NET中“using”必须和一个实现了IDisposible接口的实体类混合使用,其语法形式为:
[C#]
using(类名称 实体名称 = new 类名称()) { //这里使用…… }
[VB.NET]
Using 实体名 As New 类名称 '这儿使用这个类实体…… End Using
通过MSDN的说明我们可以知道using的本质是一个try……finally:
[C#]
类名称 实体名称 = null; try { 实体名称 = new 类名称(); } finally { 实体名称.Dispose(); }
[VB.NET]
Dim 实体名称 As 类名称 = Nothing Try 实体名称 = New 类名称() Finally 实体名称.Dispose() End Try
一般地,凡是使用using块的实体只能在using作用域中使用(即只允许在using的{……}中使用)。其实从真实情况下的try……finally……中也可以得出这样一个结论——因为无论如何,Finally必须被执行,也就是说Dispose被调用之后实体对象(内部一些托管或非托管对象实体等)已经被销毁,虽然“类实体”自身并未被回收,但是此类已经无法再使用了。
下面在进行一下拓展:
【例】,问以下程序运行的结果:
[C#]
class Fun:IDisposable { public int I{get;set;} public Fun() { I=1; } public void Dispose() { I=0; } } class MainTest { static int Func() { using(Fun f = new Fun()) { return f.I; } } static void Main() { Console.WriteLine(Func()); } }
[VB.NET]
Class Fun Implements IDisposable Public Property I() As Integer Get Return m_I End Get Set m_I = Value End Set End Property Private m_I As Integer Public Sub New() I = 1 End Sub Public Sub Dispose() I = 0 End Sub End Class Class MainTest Private Shared Function Func() As Integer Using f As New Fun() Return f.I End Using End Function Private Shared Sub Main() Console.WriteLine(Func()) End Sub End Class
粗看此题目,或许你会认为说因为Finally无论如何必须要执行,因此I从1变成了0,输出的也是0——其实不然!在using中如果使用了“return”之后,内部生成的代码略有变化(我们通过IL来证明):
.method private hidebysig static int32 Func() cil managed { // 代码大小 36 (0x24) .maxstack 2 .locals init ([0] class CSharp.Fun f, [1] int32 CS$1$0000, [2] bool CS$4$0001) IL_0000: nop IL_0001: newobj instance void CSharp.Fun::.ctor() IL_0006: stloc.0 .try { IL_0007: nop IL_0008: ldloc.0 IL_0009: callvirt instance int32 CSharp.Fun::get_I() IL_000e: stloc.1 IL_000f: leave.s IL_0021 } // end .try finally { IL_0011: ldloc.0 IL_0012: ldnull IL_0013: ceq IL_0015: stloc.2 IL_0016: ldloc.2 IL_0017: brtrue.s IL_0020 IL_0019: ldloc.0 IL_001a: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_001f: nop IL_0020: endfinally } // end handler IL_0021: nop IL_0022: ldloc.1 IL_0023: ret } // end of method MainTest::Func
注意“try“这一部分的代码——首先ldloc把实体类压入栈中,随后调用get_I(内部生成的函数,用于取出属性I的私有变量数值),随后弹栈把I的内容存入到CS$1$0000中(因为是值类型,因此明显是复制原有的内容)。同时注意在endfinally后ldloc.1弹栈取出CS$1$000的内容(因此仍然为1)。 我们另外可以注意到return的部分是在try……finally……之后才返回的。因此我们可以这样理解Fun静态函数中的内容:
[C#]
int C$1$0000 = 0; try { C$1$0000 = f.I; } finally { f.Dispose(); } return C$1$0000;
[VB.NET]
Dim C$1$0000 As Integer = 0 Try C$1$0000 = f.I Finally f.Dispose() End Try
Return C$1$0000
我们发现“C$1$0000”是自生成的(因为需要把p.I压入堆栈的需要)。因此实际返回的是这个临时生成的变量值并非原来的p.I(一般地函数需要返回某个值,都是需要临时在栈中生成一个空间存放这个变量值,然后在弹出返回)。
以此类推,如果try中返回的是一个引用类型(且这个引用类型在finally中受Dispose影响发生了改变),则即便临时栈中生成了一个引用副本(本质还是引用原来的实体!),并且返回这个副本,由于引用的缘故自然还是以最终的finally后受影响的为准。如下例子(读者自定比较):
[C#]
class Fun:IDisposable { public int I{get;set;} public Fun() { I=1; } public void Dispose() { I=0; } } class MainTest { static Fun Func() { using(Fun f = new Fun()) { return f; } } static void Main() { Console.WriteLine(Func().I); } }
[VB.NET]
Class Fun Implements IDisposable Public Property I() As Integer Get Return m_I End Get Set m_I = Value End Set End Property Private m_I As Integer Public Sub New() I = 1 End Sub Public Sub Dispose() I = 0 End Sub End Class Class MainTest Private Shared Function Func() As Fun Using f As New Fun() Return f End Using End Function Private Shared Sub Main() Console.WriteLine(Func().I) End Sub End Class
转化对应的IL代码:
// 代码大小 31 (0x1f) .maxstack 2 .locals init ([0] class CSharp.Fun f, [1] class CSharp.Fun CS$1$0000, [2] bool CS$4$0001) IL_0000: nop IL_0001: newobj instance void CSharp.Fun::.ctor() IL_0006: stloc.0 .try { IL_0007: nop IL_0008: ldloc.0 IL_0009: stloc.1 IL_000a: leave.s IL_001c } // end .try finally { IL_000c: ldloc.0 IL_000d: ldnull IL_000e: ceq IL_0010: stloc.2 IL_0011: ldloc.2 IL_0012: brtrue.s IL_001b IL_0014: ldloc.0 IL_0015: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_001a: nop IL_001b: endfinally } // end handler IL_001c: nop IL_001d: ldloc.1 IL_001e: ret } // end of method MainTest::Func
几乎与第一个例子差不多,只不过CS$1$0000是一个引用而非值类型而已!