生产中发生了一个奇怪的错误,我被要求调查。
该问题被追溯到在For循环中声明的几个变量,而不是在每次迭代时初始化。已经假设由于其声明的范围,它们将在每次迭代时“重置”。
有人可以解释他们为什么不会这样做吗?
(我的第一个问题,真的很期待回复。)
下面的示例显然不是有问题的代码,但反映了这种情况:
请原谅代码示例,它在编辑器预览中看起来很好吗?
for (int i =0; i< 10; i++)
{
decimal? testDecimal;
string testString;
switch( i % 2 )
{
case 0:
testDecimal = i / ( decimal ).32;
testString = i.ToString();
break;
default:
testDecimal = null;
testString = null;
break;
}
Console.WriteLine( "Loop {0}: testDecimal={1} - testString={2}", i, testDecimal , testString );
}
抱歉,不得不急于解决托儿问题。问题在于prod代码是switch语句很大并且在一些“case”中对一个类'属性进行检查,就像if(myObject.Prop!= null)然后testString = myObject.Stringval。 ..在开关的末尾,(外部)正在检查testString == null但是它保持了最后一次迭代的值,因此不是null,因为编码器假定变量在循环内被声明。
对不起,如果我的问题和示例有点过时,我接到了关于日间护理的电话,因为我正在一起敲打它。我应该提到我在循环中比较了来自两个变量的IL。那么,普遍认为“显然变量不会在每个循环中重新初始化”吗?
更多信息,变量WHERE在每次迭代时被初始化,直到有人过度热心ReSharper指出“值从未使用过”并将其删除。
答案 0 :(得分:15)
大多数,无论是在循环内部还是外部声明变量都无关紧要;明确分配规则确保无关紧要。在调试器中,您可能偶尔会看到旧值(即,如果在分配之前查看断点中的变量),但静态分析证明这不会影响执行代码。每个循环都不会重置变量,因为显然没有必要。
在IL级别,**通常*变量仅为方法声明一次 - 循环内的位置对我们程序员来说只是一种便利。
但有一个重要的例外;在捕获变量的任何时候,范围规则变得更加复杂。例如(2秒):
int value;
for (int i = 0; i < 5; i++)
{
value = i;
ThreadPool.QueueUserWorkItem(delegate { Console.WriteLine(value); });
}
Console.ReadLine();
非常不同于:
for (int i = 0; i < 5; i++)
{
int value = i;
ThreadPool.QueueUserWorkItem(delegate { Console.WriteLine(value); });
}
Console.ReadLine();
由于第二个示例中的“值”是真正每个实例,因为它被捕获。这意味着第一个例子可能会显示(例如)“4 4 4 4 4”,其中 - 第二个例子将显示0-5(按任意顺序) - 即“1 2 5 3 4”。
那么:原始代码中涉及的捕获?任何带有lambda,匿名方法或LINQ查询的内容都符合条件。
答案 1 :(得分:14)
<强>摘要强>
比较生成的IL用于在循环内声明变量和生成的IL用于声明循环外的变量,证明两种变量声明样式之间没有性能差异。 (生成的IL实际上是相同的。)
这是原始源,据说使用“更多资源”,因为变量是在循环内声明的:
using System;
class A
{
public static void Main()
{
for (int i =0; i< 10; i++)
{
decimal? testDecimal;
string testString;
switch( i % 2 )
{
case 0:
testDecimal = i / ( decimal ).32;
testString = i.ToString();
break;
default:
testDecimal = null;
testString = null;
break;
}
Console.WriteLine( "Loop {0}: testDecimal={1} - testString={2}", i, testDecimal , testString );
}
}
}
以下是低效声明来源的IL:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 8
.locals init (
[0] int32 num,
[1] valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> nullable,
[2] string str,
[3] int32 num2,
[4] bool flag)
L_0000: nop
L_0001: ldc.i4.0
L_0002: stloc.0
L_0003: br.s L_0061
L_0005: nop
L_0006: ldloc.0
L_0007: ldc.i4.2
L_0008: rem
L_0009: stloc.3
L_000a: ldloc.3
L_000b: ldc.i4.0
L_000c: beq.s L_0010
L_000e: br.s L_0038
L_0010: ldloca.s nullable
L_0012: ldloc.0
L_0013: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int32)
L_0018: ldc.i4.s 0x20
L_001a: ldc.i4.0
L_001b: ldc.i4.0
L_001c: ldc.i4.0
L_001d: ldc.i4.2
L_001e: newobj instance void [mscorlib]System.Decimal::.ctor(int32, int32, int32, bool, uint8)
L_0023: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Division(valuetype [mscorlib]System.Decimal, valuetype [mscorlib]System.Decimal)
L_0028: call instance void [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>::.ctor(!0)
L_002d: nop
L_002e: ldloca.s num
L_0030: call instance string [mscorlib]System.Int32::ToString()
L_0035: stloc.2
L_0036: br.s L_0044
L_0038: ldloca.s nullable
L_003a: initobj [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>
L_0040: ldnull
L_0041: stloc.2
L_0042: br.s L_0044
L_0044: ldstr "Loop {0}: testDecimal={1} - testString={2}"
L_0049: ldloc.0
L_004a: box int32
L_004f: ldloc.1
L_0050: box [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>
L_0055: ldloc.2
L_0056: call void [mscorlib]System.Console::WriteLine(string, object, object, object)
L_005b: nop
L_005c: nop
L_005d: ldloc.0
L_005e: ldc.i4.1
L_005f: add
L_0060: stloc.0
L_0061: ldloc.0
L_0062: ldc.i4.s 10
L_0064: clt
L_0066: stloc.s flag
L_0068: ldloc.s flag
L_006a: brtrue.s L_0005
L_006c: ret
}
以下是声明循环外变量的来源:
using System;
class A
{
public static void Main()
{
decimal? testDecimal;
string testString;
for (int i =0; i< 10; i++)
{
switch( i % 2 )
{
case 0:
testDecimal = i / ( decimal ).32;
testString = i.ToString();
break;
default:
testDecimal = null;
testString = null;
break;
}
Console.WriteLine( "Loop {0}: testDecimal={1} - testString={2}", i, testDecimal , testString );
}
}
}
这是IL声明循环外的变量:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 8
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal> nullable,
[1] string str,
[2] int32 num,
[3] int32 num2,
[4] bool flag)
L_0000: nop
L_0001: ldc.i4.0
L_0002: stloc.2
L_0003: br.s L_0061
L_0005: nop
L_0006: ldloc.2
L_0007: ldc.i4.2
L_0008: rem
L_0009: stloc.3
L_000a: ldloc.3
L_000b: ldc.i4.0
L_000c: beq.s L_0010
L_000e: br.s L_0038
L_0010: ldloca.s nullable
L_0012: ldloc.2
L_0013: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int32)
L_0018: ldc.i4.s 0x20
L_001a: ldc.i4.0
L_001b: ldc.i4.0
L_001c: ldc.i4.0
L_001d: ldc.i4.2
L_001e: newobj instance void [mscorlib]System.Decimal::.ctor(int32, int32, int32, bool, uint8)
L_0023: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Division(valuetype [mscorlib]System.Decimal, valuetype [mscorlib]System.Decimal)
L_0028: call instance void [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>::.ctor(!0)
L_002d: nop
L_002e: ldloca.s num
L_0030: call instance string [mscorlib]System.Int32::ToString()
L_0035: stloc.1
L_0036: br.s L_0044
L_0038: ldloca.s nullable
L_003a: initobj [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>
L_0040: ldnull
L_0041: stloc.1
L_0042: br.s L_0044
L_0044: ldstr "Loop {0}: testDecimal={1} - testString={2}"
L_0049: ldloc.2
L_004a: box int32
L_004f: ldloc.0
L_0050: box [mscorlib]System.Nullable`1<valuetype [mscorlib]System.Decimal>
L_0055: ldloc.1
L_0056: call void [mscorlib]System.Console::WriteLine(string, object, object, object)
L_005b: nop
L_005c: nop
L_005d: ldloc.2
L_005e: ldc.i4.1
L_005f: add
L_0060: stloc.2
L_0061: ldloc.2
L_0062: ldc.i4.s 10
L_0064: clt
L_0066: stloc.s flag
L_0068: ldloc.s flag
L_006a: brtrue.s L_0005
L_006c: ret
}
我将分享这个秘密,除了指定.locals init ( ... )
的顺序之外,IL完全相同。循环中的DECLARING变量导致NO ADDITIONAL IL。
答案 2 :(得分:8)
你不应该把声明放在for循环中。它吸引了额外的资源来反复创建一个变量,当你应该做的就是每次迭代都清除变量。
不,它不!应该完成与你的建议完全相反的建议。但即使重置变量更有效,在更严格的范围内声明变量也要清楚得多。并且随着时间的推移,清晰度胜过微优化(几乎)。此外,一个变量,一个用法。不要不必要地重用变量。
也就是说,变量不会在此重置或重新初始化 - 实际上,它们甚至不是由C#初始化的!要解决这个问题,只需初始化它们即可完成。
答案 3 :(得分:2)
以下是代码的输出:
Loop 0: testDecimal=0 - testString=0
Loop 1: testDecimal= - testString=
Loop 2: testDecimal=6.25 - testString=2
Loop 3: testDecimal= - testString=
Loop 4: testDecimal=12.5 - testString=4
Loop 5: testDecimal= - testString=
Loop 6: testDecimal=18.75 - testString=6
Loop 7: testDecimal= - testString=
Loop 8: testDecimal=25 - testString=8
Loop 9: testDecimal= - testString=
我没有在发布的来源中更改任何内容来生成此输出。请注意,它也没有抛出异常。
答案 4 :(得分:0)
您是否收到NullReferenceException错误?
从上面的代码中,您将在循环的每个奇数编号迭代中得到该错误,因为您在将变量指定为null后尝试打印变量。
答案 5 :(得分:0)
这里发生了一些奇怪的事情,如果它们从未被初始化,那么它应该抛出一个编译错误。
当我运行你的代码时,我得到了我所期望的,奇数循环和偶数循环上的正确数字都没有。
答案 6 :(得分:-1)
这确实让我感到惊讶。我原本认为范围会在“for”循环中发生变化。似乎并非如此。这些值将被保留。编译器看起来足够智能,可以在首次输入“for”循环时声明变量一次。
我同意以前的帖子,你不应该把声明放在“for”循环中。如果你初始化变量,你将在每个循环中吸收资源。
但是如果你将“for”循环的内部部分打破到一个函数(我知道这仍然很糟糕)。你超出了范围,每次都会创建变量。
private void LoopTest()
{
for (int i =0; i< 10; i++)
{
DoWork(i);
}
}
private void Work(int i)
{
decimal? testDecimal;
string testString;
switch (i % 2)
{
case 0:
testDecimal = i / (decimal).32;
testString = i.ToString();
break;
default:
testDecimal = null;
testString = null;
break;
}
Console.WriteLine( "Loop {0}: testDecimal={1} - testString={2}", i, testDecimal , testString );
}
至少我学到了新东西。以及在循环中声明变量的确有多糟糕。