在性能方面,让我们尝试一下具体的例子:
public void Method1()
{
foreach(int i in Enumerable.Range(0, 10))
{
int x = i * i;
StringBuilder sb = new StringBuilder();
sb.Append(x);
Console.WriteLine(sb);
}
}
public void Method2()
{
int x;
StringBuilder sb;
foreach(int i in Enumerable.Range(0, 10))
{
x = i * i;
sb = new StringBuilder();
sb.Append(x);
Console.WriteLine(sb);
}
}
我特意选择了值类型和引用类型,以防影响事物。现在,他们的 IL:
.method public hidebysig instance void Method1() cil managed
{
.maxstack 2
.locals init (
[0] int32 i,
[1] int32 x,
[2] class [mscorlib]System.Text.StringBuilder sb,
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
L_0000: ldc.i4.0
L_0001: ldc.i4.s 10
L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
L_000d: stloc.3
L_000e: br.s L_002f
L_0010: ldloc.3
L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
L_0016: stloc.0
L_0017: ldloc.0
L_0018: ldloc.0
L_0019: mul
L_001a: stloc.1
L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0020: stloc.2
L_0021: ldloc.2
L_0022: ldloc.1
L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
L_0028: pop
L_0029: ldloc.2
L_002a: call void [mscorlib]System.Console::WriteLine(object)
L_002f: ldloc.3
L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_0035: brtrue.s L_0010
L_0037: leave.s L_0043
L_0039: ldloc.3
L_003a: brfalse.s L_0042
L_003c: ldloc.3
L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0042: endfinally
L_0043: ret
.try L_000e to L_0039 finally handler L_0039 to L_0043
}
.method public hidebysig instance void Method2() cil managed
{
.maxstack 2
.locals init (
[0] int32 x,
[1] class [mscorlib]System.Text.StringBuilder sb,
[2] int32 i,
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
L_0000: ldc.i4.0
L_0001: ldc.i4.s 10
L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
L_000d: stloc.3
L_000e: br.s L_002f
L_0010: ldloc.3
L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
L_0016: stloc.2
L_0017: ldloc.2
L_0018: ldloc.2
L_0019: mul
L_001a: stloc.0
L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0020: stloc.1
L_0021: ldloc.1
L_0022: ldloc.0
L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
L_0028: pop
L_0029: ldloc.1
L_002a: call void [mscorlib]System.Console::WriteLine(object)
L_002f: ldloc.3
L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_0035: brtrue.s L_0010
L_0037: leave.s L_0043
L_0039: ldloc.3
L_003a: brfalse.s L_0042
L_003c: ldloc.3
L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0042: endfinally
L_0043: ret
.try L_000e to L_0039 finally handler L_0039 to L_0043
}
正如您所看到的,除了编译器碰巧选择的堆栈顺序(也可能是不同的顺序)之外,它绝对没有任何效果。反过来,实际上没有任何东西是一个人给予抖动以充分利用另一个人没有给予的东西。
除此之外,还有一种差异。
In my Method1()
, x
and sb
范围为foreach
,并且不能在其外部有意或无意地访问。
In my Method2()
, x
and sb
在编译时不知道是否可以在foreach
(编译器不知道foreach
将执行至少一个循环),因此禁止使用它。
到目前为止,没有真正的区别。
I can然而分配和使用x
and/or sb
之外的foreach
。一般来说,我会说,大多数时候这可能是很糟糕的范围界定,所以我倾向于Method1
,但我可能有一些合理的理由想要引用它们(更现实的是,如果它们不可能未分配),在这种情况下我会选择Method2
.
不过,这只是每个代码如何扩展或不扩展的问题,而不是编写的代码之间的差异。真的,没有什么区别。