c# - 无法分配给项目,因为它是一个 foreach 迭代变量
问题描述
为什么我们不能在 foreach 循环中为局部变量赋值?
我知道这不会更新集合,因为我们只是在更改局部变量所指的对象。
如果您使用 while 循环实现相同的功能,它允许您更新局部变量。
class Program
{
static void Main(string[] args)
{
IEnumerable<string> enumerable = new List<string> { "a", "b", "c" };
//This foreach loop does not compile:
foreach (string item in enumerable)
{
item = "d"; //"cannot assign to item because it is a foreach iteration variable"
}
//The equivalent C# code does compile and allows assinging a value to item:
var enumerator = enumerable.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
string item = (string)enumerator.Current;
item = "d";
}
}
finally
{
enumerator.Dispose();
}
}
}
编辑:
这个问题与可能的重复问题不同,因为它询问为什么我们不能修改迭代变量,而在幕后它只是一个指向与enumerator.Current
.
解决方案
我修改了你的代码,所以它们实际上是等价的:
class Program
{
static void Main(string[] args)
{
IEnumerable<string> enumerable = new List<string> { "a", "b", "c" };
foreach (string element in enumerable)
{
object item = element;
item = "d";
}
IEnumerator enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
object item = enumerator.Current;
item = "d";
}
}
}
如您所见,我们没有以任何形式分配给只读 enumerator.Current
当您编写 foreach 时,编译器会为您重写代码,使其看起来像这样:
IEnumerable<string> enumerable = new List<string> { "a", "b", "c" };
//you write
foreach (string element in enumerable)
{
Console.WriteLine(element);
}
//the compiler writes
string element;
var enumerator = enumerable.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
element = enumerator.Current;
//begin your foreach body code
Console.Write(element);
//end your foreach body code
}
}
finally
{
enumerator.Dispose();
}
对于每个通过的版本,C# 都会以几种方式发展。新的处理结构当然可以添加到语言中,但“语法糖”通常是通过让编译器能够将您编写的代码翻译成已经存在的功能(例如foreach(x in y)
成为y.GetEnumerator(); while(..){ x = enumerator.Current; ... }
.
同样,字符串插值$"There are {list.Count:N2} items" being transformable to the implemented-first
string.Format("There are {0:N2} items, list.Count")`。
确实,如果考虑编译器的工作是将已编写的内容转换为已编写的其他内容,那么即使是语法糖化也是新处理结构的一个示例,因为一切都已经编写好了(一直到硬件) )
为了检查编译器如何重写 foreach,我编写了两个方法:
static void ForEachWay(){
var enumerable = new List<string>();
enumerable.Add("a");
enumerable.Add("b");
enumerable.Add("c");
//you write
foreach (string element in enumerable)
{
Console.WriteLine(element);
}
}
static void EnumWayWithLoopVar(){
var enumerable = new List<string>();
enumerable.Add("a");
enumerable.Add("b");
enumerable.Add("c");
string e;
var enumerator = enumerable.GetEnumerator();
try{
while (enumerator.MoveNext())
{
e = enumerator.Current;
Console.Write(e);
}
}finally{
enumerator.Dispose();
}
}
我使用 .NET SDK 4.7.2 附带的 csc.exe 进行编译,然后使用 ILDASM 可视化生成的 MSIL(屏幕截图显示并排差异)。方法、设置、变量声明等的初始部分是相同的,但对于无操作:
和循环体一样:
唯一可辨别的区别在于处置。我对追查原因不太感兴趣。
因此,很明显编译器会以所示方式重写该方法。您最初的问题只能由编写编译器的人真正回答,但在我看来,这是禁止分配的扩展,这似乎是合乎逻辑的enumerator.Current
- 阻止这种情况是没有意义的,但允许您分配给变量相当于在你的 foreach 循环中声明的。同样清楚的是,这是一个直接评估的特殊情况,从错误消息与 foreach 循环特别讨论的方式来看。
我还要说(意见部分),防止分配给 foreach 循环迭代器变量可以防止伤害/意外后果/晦涩的错误,没有我可以辨别的缺点。如果你想要一个变量,你可以重新分配;再做一个
推荐阅读
- itext - 如何在 Itext7 中使用自定义颜色和自定义字体
- android - 如何使用 AWS C++ SDK 为 Android 构建 Qt 应用程序?
- netty - Netty SctpMultiChannel
- android - IONIC 3 android应用程序启动延迟
- authentication - Wireshark - 从连接的 AP 捕获 AUTH&ASSOC 数据包
- ios - 裁剪 UIImage 没有产生预期的作物 - 迅速?
- aws-glue - AWS Glue 作业 python 脚本
- java - 当用户单击卡片视图时,如何从 Firebase 实时数据库中删除卡片视图上显示的特定记录。
- python - 如何在不使用函数的情况下在 python 中打印名称的反面?
- android - 圆角 ExpandableListView