迭代变量如何只读?
How is the iteration variable readonly?
在C#规范的8.8.4中,提供了这个例子:
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
}
}
它还说:
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.
迭代变量如何设为只读?
在 C# 中,这里不能使用 readonly,const 也不起作用。
这是我做的一个例子。
我查看了 CIL 代码,但看不到任何地方将迭代变量设置为只读:
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
迭代变量是只读的,因为写入它是一个错误。试一试,你就会知道。
它没有创建 readonly
字段,文档中也没有说它创建了 readonly
字段。它不可能是 readonly
字段,因为它不是字段。
现在,这是一个微妙的问题。假设 v
是可变值类型,并且您调用一个方法来改变 this
的字段,传递 v
。预测会发生什么。现在试试看;你是对的吗?你能解释一下发生了什么吗?您现在如何看待 v
是 "read-only" 的说法?你会说这是一个错误,还是正确的行为?
现在用 readonly
字段尝试同样的操作,看看结果如何。您认为这是正确的行为吗?
编译器中有特殊情况代码,它对 foreach
块中的迭代变量强制执行只读约束。它不对应于语言中公开的任何修饰符,因此您不能在此特定语法之外显式地将局部变量声明为只读。
从概念上讲,此约束在 扩展之前应用。也就是说,如果对迭代变量进行任何赋值,编译器就会产生错误。否则代码被扩展。在扩展代码中,v
没有特别的限制,因为它只是一个常规的局部变量。因此IL中也不存在约束。
那么为什么 有这种特殊情况的只读约束和foreach
-语法?只有语言设计者才能回答这个问题,但我想这只是为了避免混淆。如果迭代器变量是可分配的,您可能认为您能够以这种方式修改实际集合,但实际上什么也不会发生,因为底层枚举器是只读的。
在C#规范的8.8.4中,提供了这个例子:
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
}
}
它还说:
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.
迭代变量如何设为只读?
在 C# 中,这里不能使用 readonly,const 也不起作用。
这是我做的一个例子。
我查看了 CIL 代码,但看不到任何地方将迭代变量设置为只读:
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
迭代变量是只读的,因为写入它是一个错误。试一试,你就会知道。
它没有创建 readonly
字段,文档中也没有说它创建了 readonly
字段。它不可能是 readonly
字段,因为它不是字段。
现在,这是一个微妙的问题。假设 v
是可变值类型,并且您调用一个方法来改变 this
的字段,传递 v
。预测会发生什么。现在试试看;你是对的吗?你能解释一下发生了什么吗?您现在如何看待 v
是 "read-only" 的说法?你会说这是一个错误,还是正确的行为?
现在用 readonly
字段尝试同样的操作,看看结果如何。您认为这是正确的行为吗?
编译器中有特殊情况代码,它对 foreach
块中的迭代变量强制执行只读约束。它不对应于语言中公开的任何修饰符,因此您不能在此特定语法之外显式地将局部变量声明为只读。
从概念上讲,此约束在 扩展之前应用。也就是说,如果对迭代变量进行任何赋值,编译器就会产生错误。否则代码被扩展。在扩展代码中,v
没有特别的限制,因为它只是一个常规的局部变量。因此IL中也不存在约束。
那么为什么 有这种特殊情况的只读约束和foreach
-语法?只有语言设计者才能回答这个问题,但我想这只是为了避免混淆。如果迭代器变量是可分配的,您可能认为您能够以这种方式修改实际集合,但实际上什么也不会发生,因为底层枚举器是只读的。