据我了解,C#的foreach迭代变量是不可变的。
这意味着我不能像这样修改迭代器:
foreach (Position Location in Map)
{
//We want to fudge the position to hide the exact coordinates
Location = Location + Random(); //Compiler Error
Plot(Location);
}
我不能直接修改迭代器变量,而是必须使用for循环
for (int i = 0; i < Map.Count; i++)
{
Position Location = Map[i];
Location = Location + Random();
Plot(Location);
i = Location;
}
来自C ++背景,我认为foreach是for循环的替代品。但是由于上述限制,我通常会回退使用for循环。
我很好奇,使迭代器不可变的原理是什么?
编辑:
这个问题更多的是一个好奇的问题,而不是一个编码问题。我很感激编码答案,但我不能将它们标记为答案。
此外,上述示例过于简化。这是我想要做的C ++示例:
// The game's rules:
// - The "Laser Of Death (tm)" moves around the game board from the
// start area (index 0) until the end area (index BoardSize)
// - If the Laser hits a teleporter, destroy that teleporter on the
// board and move the Laser to the square where the teleporter
// points to
// - If the Laser hits a player, deal 15 damage and stop the laser.
for (int i = 0; i < BoardSize; i++)
{
if (GetItem(Board[i]) == Teleporter)
{
TeleportSquare = GetTeleportSquare(Board[i]);
SetItem(Board[i], FreeSpace);
i = TeleportSquare;
}
if (GetItem(Board[i]) == Player)
{
Player.Life -= 15;
break;
}
}
我无法在C#的foreach中执行上述操作,因为迭代器是不可变的。我想(如果我错了,请纠正我),这是针对foreach语言的设计。
我对为什么foreach迭代器是不可变的感兴趣。
答案 0 :(得分:25)
让我们从一个愚蠢但说明性的例子开始:
Object o = 15;
o = "apples";
在任何时候我们都不会觉得我们只是把数字15变成了一串苹果。我们知道o
只是一个指针。现在让我们以迭代器的形式执行此操作。
int[] nums = { 15, 16, 17 };
foreach (Object o in nums) {
o = "apples";
}
同样,这真的没有成就。或者至少它 什么都没有完成编译。它肯定不会将我们的字符串插入到int数组中 - 这是不允许的,我们知道o
无论如何都只是一个指针。
我们举个例子:
foreach (Position Location in Map)
{
//We want to fudge the position to hide the exact coordinates
Location = Location + Random(); //Compiler Error
Plot(Location);
}
如果要进行编译,示例中的Location
会引用Map
中的值,但随后您将其更改为引用新的Position
(由foreach (Position Location in Map)
{
//We want to fudge the position to hide the exact coordinates
Position Location2 = Location + Random(); //No more Error
Plot(Location2);
}
隐式创建加法运算符)。在功能上它等同于此(编译):
{{1}}
那么,为什么Microsoft会禁止您重新分配用于迭代的指针?清楚一件事 - 你不希望人们认为他们已经改变了你在循环中的位置。易于实现另一个:变量可能隐藏一些内部逻辑,指示正在进行的循环的状态。
但更重要的是,您没有理由想要分配给它。它表示循环序列的当前元素。如果您遵循Coding Horror,为其分配值会破坏“单一责任原则”或Curly's Law。变量应该仅仅意味着一件事。
答案 1 :(得分:14)
如果变量是可变的,那可能会给人一种不正确的印象。例如:
string[] names = { "Jon", "Holly", "Tom", "Robin", "William" };
foreach (string name in names)
{
name = name + " Skeet";
}
有些人可能认为会改变数组内容。它达到了一点,但这可能是一个原因。我今晚会在我的注释规范中查一查......
答案 2 :(得分:10)
我认为它是人为的限制,不需要这么做。为证明这一点,请将此代码纳入考虑范围,并为您的问题提供可能的解决方案。问题是asign,但对象的内部更改不会出现问题:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<MyObject> colection =new List<MyObject>{ new MyObject{ id=1, Name="obj1" },
new MyObject{ id=2, Name="obj2"} };
foreach (MyObject b in colection)
{
// b += 3; //Doesn't work
b.Add(3); //Works
}
}
class MyObject
{
public static MyObject operator +(MyObject b1, int b2)
{
return new MyObject { id = b1.id + b2, Name = b1.Name };
}
public void Add(int b2)
{
this.id += b2;
}
public string Name;
public int id;
}
}
}
我不知道这个现象,因为我总是按照我描述的方式修改对象。
答案 3 :(得分:2)
foreach语句适用于Enumerable。与Enumerable不同的是它不了解整个集合,它几乎是盲目的。
这意味着它不知道接下来会发生什么,或者确实在下一个迭代周期之前有什么东西。这就是为什么你看到.ToList()很多,所以人们可以抓住Enumerable的数量。
出于某种原因,我不确定,这意味着您需要确保在尝试移动枚举器时收集不会发生变化。
答案 4 :(得分:2)
修改集合时,修改可能会产生不可预测的副作用。枚举器无法知道如何正确处理这些副作用,因此它们使集合不可变。
答案 5 :(得分:1)
使用IEnumerable对象的条件是,在使用Enumerable访问它时,底层集合不得更改。您可以假设Enumerable对象是原始集合的快照。因此,如果您在枚举时尝试更改集合,则会抛出异常。但是,Enumeration中的获取对象根本不是不可变的。
由于foreach循环中使用的变量是循环块的本地变量,因此该变量在块外不可用。