是'With ... End With'真的更有效率吗?

Kum*_*mba 4 vb.net cil

所以我正在玩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语句创建的临时引用大致相同,因此性能几乎没有增益.

Guf*_*ffa 5

查看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完全优化存储.

无论哪种方式,如果它仍然创建另一个变量,那是一个非常便宜的操作.如果有任何性能差异,那么它非常小,并且它可能会下降.