所以我正在玩ILDASM,并注意到一个奇怪的是我在Google上找不到一个非常好的解释.
似乎在VB.NET中使用With块时,得到的MSIL大于w/o.所以这让我想问,With Blocks真的更有效吗?MSIL是JIT到本机机器代码的东西,所以较小的代码大小应该意味着更高效的代码,对吧?
这是两个类(Class2和Class3)的示例,它们为Class1的实例设置相同的值.Class2没有With块,而Class3使用With.Class1有六个属性,涉及6个私有成员.每个成员都是特定的数据类型,它都是此测试用例的一部分.
Friend Class Class2
Friend Sub New()
Dim c1 As New Class1
c1.One = "foobar"
c1.Two = 23009
c1.Three = 3987231665
c1.Four = 2874090071765301873
c1.Five = 3.1415973801462975
c1.Six = "a"c
End Sub
End Class
Friend Class Class3
Friend Sub New()
Dim c1 As New Class1
With c1
.One = "foobar"
.Two = 23009
.Three = 3987231665
.Four = 2874090071765301873
.Five = 3.1415973801462975
.Six = "a"c
End With
End Sub
End Class
Run Code Online (Sandbox Code Playgroud)
这是Class2的结果MSIL:
.method assembly specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 84 (0x54)
.maxstack 2
.locals init ([0] class WindowsApplication1.Class1 c1)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: newobj instance void WindowsApplication1.Class1::.ctor()
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: ldstr "foobar"
IL_0012: callvirt instance void WindowsApplication1.Class1::set_One(string)
IL_0017: ldloc.0
IL_0018: ldc.i4 0x59e1
IL_001d: callvirt instance void WindowsApplication1.Class1::set_Two(int16)
IL_0022: ldloc.0
IL_0023: ldc.i4 0xeda853b1
IL_0028: callvirt instance void WindowsApplication1.Class1::set_Three(uint32)
IL_002d: ldloc.0
IL_002e: ldc.i8 0x27e2d1b1540c3a71
IL_0037: callvirt instance void WindowsApplication1.Class1::set_Four(uint64)
IL_003c: ldloc.0
IL_003d: ldc.r8 3.1415973801462975
IL_0046: callvirt instance void WindowsApplication1.Class1::set_Five(float64)
IL_004b: ldloc.0
IL_004c: ldc.i4.s 97
IL_004e: callvirt instance void WindowsApplication1.Class1::set_Six(char)
IL_0053: ret
} // end of method Class2::.ctor
Run Code Online (Sandbox Code Playgroud)
这是Class3的MSIL:
.method assembly specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 88 (0x58)
.maxstack 2
.locals init ([0] class WindowsApplication1.Class1 c1,
[1] class WindowsApplication1.Class1 VB$t_ref$L0)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: newobj instance void WindowsApplication1.Class1::.ctor()
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: ldstr "foobar"
IL_0014: callvirt instance void WindowsApplication1.Class1::set_One(string)
IL_0019: ldloc.1
IL_001a: ldc.i4 0x59e1
IL_001f: callvirt instance void WindowsApplication1.Class1::set_Two(int16)
IL_0024: ldloc.1
IL_0025: ldc.i4 0xeda853b1
IL_002a: callvirt instance void WindowsApplication1.Class1::set_Three(uint32)
IL_002f: ldloc.1
IL_0030: ldc.i8 0x27e2d1b1540c3a71
IL_0039: callvirt instance void WindowsApplication1.Class1::set_Four(uint64)
IL_003e: ldloc.1
IL_003f: ldc.r8 3.1415973801462975
IL_0048: callvirt instance void WindowsApplication1.Class1::set_Five(float64)
IL_004d: ldloc.1
IL_004e: ldc.i4.s 97
IL_0050: callvirt instance void WindowsApplication1.Class1::set_Six(char)
IL_0055: ldnull
IL_0056: stloc.1
IL_0057: ret
} // end of method Class3::.ctor
Run Code Online (Sandbox Code Playgroud)
我能够一眼就看出的唯一主要区别是ldloc.1操作码的使用ldloc.0.根据MSDN,这两者之间的差异可以忽略不计,ldloc.0是一种有效的方法,ldloc用于访问索引0处的局部变量,并且ldloc.1相同,仅针对索引1.
请注意,Class3的代码大小为88而不是84.这些来自Release/Optimized构建.内置VB Express 2010,.NET 4.0 Framework Client Profile.
思考?
编辑:
想要为这些线程上的绊脚石添加答案的通用要点,正如我所理解的那样.
明智使用With ... End With:
With ObjectA.Property1.SubProperty7.SubSubProperty4
.SubSubSubProperty1 = "Foo"
.SubSubSubProperty2 = "Bar"
.SubSubSubProperty3 = "Baz"
.SubSubSubProperty4 = "Qux"
End With
Run Code Online (Sandbox Code Playgroud)
不合理使用With ... End With:
With ObjectB
.Property1 = "Foo"
.Property2 = "Bar"
.Property3 = "Baz"
.Property4 = "Qux"
End With
Run Code Online (Sandbox Code Playgroud)
原因是因为使用ObjectA的例子,你会让几个成员失望,并且该成员的每个分辨率都需要一些工作,所以只需要一次解析引用并将最终引用粘贴到一个临时变量中(这就是With真正的),这加快了访问该对象深处隐藏的属性/方法的速度.
ObjectB效率不高,因为你的深度只有一个级别.每个分辨率与访问With语句创建的临时引用大致相同,因此性能几乎没有增益.
查看IL代码,该With块的作用基本上是:
Friend Class Class3
Friend Sub New()
Dim c1 As New Class1
Dim temp as Class1 = c1
temp.One = "foobar"
temp.Two = 23009
temp.Three = 3987231665
temp.Four = 2874090071765301873
temp.Five = 3.1415973801462975
temp.Six = "a"c
temp = Nothing
End Sub
End Class
Run Code Online (Sandbox Code Playgroud)
但重要的是JIT编译器对此做了什么.语言编译器没有做太多优化,主要是留给JIT编译器.最有可能的是,它会发现变量c1不会用于创建另一个变量以外的任何变量,并且c1完全优化存储.
无论哪种方式,如果它仍然创建另一个变量,那是一个非常便宜的操作.如果有任何性能差异,那么它非常小,并且它可能会下降.