Так что я играю с ILDASM и заметил странность, которой не могу найти действительно хорошее объяснение в Google.
Кажется, что при использовании блоков With в VB.NET результирующий MSIL будет больше, чем без. Так что это заставляет меня спросить, действительно ли 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
Вот полученный MSIL для Class2:
.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
А вот MSIL для Class3:
.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
Единственное существенное отличие, которое я могу заметить с первого взгляда, - это использование кода операции ldloc.1
над ldloc.0
. Согласно MSDN, разница между этими двумя значениями незначительна: ldloc.0
является эффективным методом использования ldloc
для доступа к локальной переменной с индексом 0, а ldloc.1
- тем же, только для индекса 1.
Обратите внимание, что размер кода Class3 равен 88 против 84. Они взяты из релизных / оптимизированных сборок. Встроенный VB Express 2010, клиентский профиль .NET 4.0 Framework.
Мысли?
EDIT:
Хотелось бы добавить тем, кто спотыкается в этой теме, общую суть ответов, насколько я их понимаю.
Разумное использование With ... End With
:
With ObjectA.Property1.SubProperty7.SubSubProperty4
.SubSubSubProperty1 = "Foo"
.SubSubSubProperty2 = "Bar"
.SubSubSubProperty3 = "Baz"
.SubSubSubProperty4 = "Qux"
End With
Неразумное использование With ... End With
:
With ObjectB
.Property1 = "Foo"
.Property2 = "Bar"
.Property3 = "Baz"
.Property4 = "Qux"
End With
Причина в том, что в примере ObjectA вы опускаете несколько членов, и каждое разрешение этого члена требует некоторой работы, поэтому, только разрешая ссылки один раз и вставляя окончательную ссылку во временную переменную (что все With
на самом деле), это ускоряет доступ к свойствам / методам, скрытым глубоко в этом объекте.
ObjectB не так эффективен, потому что вы углубляетесь только на один уровень. Каждое разрешение примерно такое же, как и доступ к временной ссылке, созданной оператором With
, поэтому прирост производительности практически отсутствует.