这两个代码块的背景有何不同?一个被认为比另一个更“好”吗?
我的想法是Example2可能会更糟,因为它可能不得不等待垃圾收集器处理该项目,但我不知道垃圾收集器是否知道这是否属实。
例1:
ListItem item;
for (int i = 1; i <= 32; i++)
{
item = new ListItem();
//do some stuff
}
例2:
for (int i = 1; i <= 32; i++)
{
ListItem item = new ListItem();
//do some stuff
}
答案 0 :(得分:10)
我已将您的代码复制到Visual Studio中,编译它,然后查看生成的IL。这是从例1生成的IL:
.method private hidebysig static void One() cil managed
{
// Code size 30 (0x1e)
.maxstack 2
.locals init ([0] class WinTest.ListItem item,
[1] int32 i,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.1
IL_0003: br.s IL_0011
IL_0005: nop
IL_0006: newobj instance void WinTest.ListItem::.ctor()
IL_000b: stloc.0
IL_000c: nop
IL_000d: ldloc.1
IL_000e: ldc.i4.1
IL_000f: add
IL_0010: stloc.1
IL_0011: ldloc.1
IL_0012: ldc.i4.s 32
IL_0014: cgt
IL_0016: ldc.i4.0
IL_0017: ceq
IL_0019: stloc.2
IL_001a: ldloc.2
IL_001b: brtrue.s IL_0005
IL_001d: ret
} // end of method Program::One
这是从例2生成的IL:
.method private hidebysig static void Two() cil managed
{
// Code size 30 (0x1e)
.maxstack 2
.locals init ([0] int32 i,
[1] class WinTest.ListItem item,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: br.s IL_0011
IL_0005: nop
IL_0006: newobj instance void WinTest.ListItem::.ctor()
IL_000b: stloc.1
IL_000c: nop
IL_000d: ldloc.0
IL_000e: ldc.i4.1
IL_000f: add
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: ldc.i4.s 32
IL_0014: cgt
IL_0016: ldc.i4.0
IL_0017: ceq
IL_0019: stloc.2
IL_001a: ldloc.2
IL_001b: brtrue.s IL_0005
IL_001d: ret
} // end of method Program::Two
据我了解,它们是相同的,除了本地人以相反的顺序声明(并因此被访问)这一事实。我不认为这会对性能产生任何影响。
答案 1 :(得分:4)
这取决于“//做一些事情”代表什么。
在一个简单的程序中,两个示例都将编译为相同的MSIL字节代码。
但是如果在循环中创建一个匿名委托,可能是在另一个线程中执行,则创建引用变量“item”,那么在循环内部或外部声明“item”是很重要的。如果,如在示例2中,“item”在循环内声明,那么当委托运行时,它将看到在创建委托的循环的迭代中分配的“item”的值(这很可能是在这些情况)。如果,如在示例1中,“item”在循环之外被声明,则委托将在其执行时看到分配的值,该值可以来自比创建委托的迭代更晚的迭代。这会导致混乱的竞争条件。
基本上,虽然C#编译的MSIL字节代码不代表变量范围,但范围在C#中是有意义的,并且可以影响其各种语法糖的行为。
答案 2 :(得分:3)
令人惊讶的是,我的测试显示示例2更快。
输出。案例1是单一声明。案例2是循环声明。
Case 1: 10.280418100
Case 2: 10.264818000 99.848254226%
Case 1: 10.592418600
Case 2: 10.140017800 95.729013202%
Case 1: 10.233618000
Case 2: 10.108817800 98.780487996%
Case 1: 10.155617800
Case 2: 10.046417600 98.924731098%
Case 1: 10.503818600
Case 2: 10.246319800 97.548522020%
Case 1: 10.243018400
Case 2: 10.030817600 97.928337217%
Case 1: 10.077617700
Case 2: 10.218017900 101.393188392%
Case 1: 10.303019300
Case 2: 10.526318800 102.167320991%
Case 1: 10.353619900
Case 2: 10.276219400 99.252430544%
Case 1: 10.264818100
Case 2: 10.202417900 99.392096388%
Case 1 Total: 103.007984500
Case 2 Total: 102.060182600 99.079875308%
代码:
Public Sub Main()
Dim Case1Total As Double = 0
Dim Case2Total As Double = 0
For i As Integer = 1 To 10
Dim Case1 As Double = MeasureTime(AddressOf Case1Method).TotalSeconds
Case1Total += Case1
Console.WriteLine("Case 1: {0:N9}", Case1)
Dim Case2 As Double = MeasureTime(AddressOf Case2Method).TotalSeconds
Case2Total += Case2
Console.WriteLine("Case 2: {0:N9} {1:N9}%", Case2, 100 * Case2 / Case1)
Next i
Console.WriteLine()
Console.WriteLine("Case 1 Total: {0:N9}", Case1Total)
Console.WriteLine("Case 2 Total: {0:N9} {1:N9}%", Case2Total, 100 * Case2Total / Case1Total)
Console.ReadLine()
End Sub
Private Function MeasureTime(Method As Action) As TimeSpan
Dim StartTime As Date = Date.Now
Method()
Return Date.Now - StartTime
End Function
Private Sub Case1Method()
Dim o As Label
For i As Integer = 0 To Limit
o = New Label
o.Text = "Label" & i.ToString
o.Dispose()
Next
End Sub
Private Sub Case2Method()
For i As Integer = 0 To Limit
Dim o As New Label
o.Text = "Label" & i.ToString
o.Dispose()
Next
End Sub
Private Const Limit As Integer = 1024 * 1024
答案 3 :(得分:2)
我个人的感觉是,这里没有什么区别(超出明显的范围界限)。
其中任何一个都会分配新的内存并引用它,并使前一个内存少一个参考,准备好GC / as(如果需要)。
我不认为2会等待GC,部分是因为GC不是“内联”,部分是因为无法保证item
是该实例的唯一引用并且无论如何都需要GC - 正如我所说,引用计数将会减少,就是这样。
当然这只是我对它的感觉,我会有兴趣看看其他人有什么要说的(我可以在写作中感受到一些博客文章!)
答案 4 :(得分:1)
当我保持//do some stuff
为空时,两个版本都生成完全相同的IL,因此不存在任何可能的差异。我认为如果你在那里放一些代码,生成的IL仍然是相同的。
关于GC,第二个示例可能看起来理论上可能更快,因为GC可以在执行循环的增量和比较之前收集ListItem
。但事实上,它也可以在第一种情况下做同样的事情,所以没有区别。
两个版本不相同的一个例外是使用闭包时。在第一个版本中,只有一个变量“实例”被关闭,在第二个案例中,有32个。因此存在性能差异(在第二种情况下,为每次迭代创建闭包对象),但意义上的差异更为重要。
一般来说,我喜欢尽可能保持变量,因为我认为它有助于读取。