问题
In 8.8.4 of the C# specification, it provides this example:
A foreach statement of the form
foreach (V v in x) embedded-statement
is then expanded to:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
It also says:
The iteration variable corresponds to a read-only local variable with a scope that extends over the embedded statement.
The variable v is read-only in the embedded statement.
How is the iteration variable made readonly?
In C# you can't use readonly here, and const doesn't work either.
Here is an example I made.
I viewed the CIL code but can't see anywhere where it makes the iteration variable readonly:
C#:
class Program
{
static void Main(string[] args)
{
var enumerable = new List<string> { "a", "b" };
foreach (string item in enumerable)
{
string x = item;
}
}
}
CIL:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 80 (0x50)
.maxstack 3
.entrypoint
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<string> enumerable,
[1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>,
[2] string item,
[3] string x
)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
IL_0006: dup
IL_0007: ldstr "a"
IL_000c: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
IL_0011: nop
IL_0012: dup
IL_0013: ldstr "b"
IL_0018: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
IL_001d: nop
IL_001e: stloc.0
IL_001f: nop
IL_0020: ldloc.0
IL_0021: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
IL_0026: stloc.1
.try
{
IL_0027: br.s IL_0035
// loop start (head: IL_0035)
IL_0029: ldloca.s 1
IL_002b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
IL_0030: stloc.2
IL_0031: nop
IL_0032: ldloc.2
IL_0033: stloc.3
IL_0034: nop
IL_0035: ldloca.s 1
IL_0037: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
IL_003c: brtrue.s IL_0029
// end loop
IL_003e: leave.s IL_004f
} // end .try
finally
{
IL_0040: ldloca.s 1
IL_0042: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
IL_0048: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_004d: nop
IL_004e: endfinally
} // end handler
IL_004f: ret
} // end of method Program::Main
回答1:
There is special-case code in the compiler which enforces the read-only constraint on the iteration variable in a foreach
block. It does not correspond to any modifier which is exposed in the language, so you can't explicitly declare local variables as read-only outside of this particular syntax.
Conceptually, this constraint is applied before the expansion. That is, if there are any assignments to the iteration variable, the compiler generates an error. Otherwise the code is expanded. In the expanded code there is no particular constraints on v
since it is just a regular local variable. Therefore the constraint does not exist in the IL either.
So why is there this special-case read-only constraint with the foreach
-syntax? Only the language designers can answer that, but I would guess it is just to avoid confusion. If the iterator variable was assignable, you might think you were able to modify the actual collection that way, but nothing would actually happen, since the underlying enumerator is read-only.
回答2:
The iteration variable is read-only because it is an error to write to it. Give it a try, and you'll see.
It doesn't make a readonly
field, and the documentation does not say that it makes a readonly
field. It cannot possibly be a readonly
field because it is not a field.
Now, here is a subtle question. Suppose v
is of mutable value type, and you call a method on the type which mutates a field of this
, passing v
. Make a prediction about what happens. Now try it; were you right? Can you explain what happened? What do you think now about the claim that v
is "read-only"? Would you say that this is a bug, or the right behavior?
Now try the same thing with a readonly
field, and see what the results are. Do you think this is the right behaviour?
来源:https://stackoverflow.com/questions/57940961/how-is-the-iteration-variable-readonly