为什么C#foreach语句中的迭代variables是只读的?
据我所知,C#的foreach迭代variables是不可变的。
这意味着我不能像这样修改迭代器:
foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Location = Location + Random(); //Compiler Error Plot(Location); }
我不能直接修改迭代器variables,而是必须使用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的devise。
我感兴趣的是为什么foreach迭代器是不可变的。
让我们从一个愚蠢的例子开始:
Object o = 15; o = "apples";
我们从来没有得到这样的印象:我们把15号变成了一串苹果。 我们知道o
只是一个指针。 现在让我们以迭代器的forms来做这件事。
int[] nums = { 15, 16, 17 }; foreach (Object o in nums) { o = "apples"; }
再次,这真的没有成就。 或者至less它不会完成编译。 它肯定不会把我们的string插入到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
(由加法运算符隐式创build)。 在function上它相当于这个(编译):
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); }
那么,为什么微软禁止你重新分配用于迭代的指针呢? 明确的一件事 – 你不想让人分配,认为他们已经改变了你在循环中的位置。 另一种方法的易用性:该variables可能会隐藏一些内部逻辑,指示正在进行的循环的状态。
但更重要的是,你没有理由要分配它。 它表示循环序列的当前元素。 如果你遵循编码恐怖(Coding Horror),给它分配一个值会打破“单一责任原则”或者“ curl法则”。 一个variables应该只意味着一件事。
如果这个variables是可变的,那可能会给人不正确的印象。 例如:
string[] names = { "Jon", "Holly", "Tom", "Robin", "William" }; foreach (string name in names) { name = name + " Skeet"; }
有些人可能会认为会改变arrays内容。 它已经达到了一点,但可能是一个原因。 我会在今晚的注释规格中查看
我认为它的人为限制,没有必要这样做。 为了certificate这一点,请将此代码纳入考虑范围,并为您的问题提供可能的解决scheme。 问题在于devise,但是对象的内部变化不存在问题:
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; } } }
我不知道这个现象,因为我总是按照我描述的方式修改这些对象。
foreach语句适用于Enumerable。 与Enumerable不同的是,它并不知道整个集合,几乎是盲目的。
这意味着它不知道接下来会发生什么,或者直到下一个迭代周期才有任何事情发生。 那为什么在那里你看到.ToList()很多,所以人们可以抓住一个枚举数。
出于某种原因,我不确定,这意味着您需要确保您的collections不会改变,因为您试图将枚举移动。
修改集合时,修改可能具有不可预知的副作用。 枚举者无法知道如何正确处理这些副作用,所以他们使集合不可变。
另请参阅此问题: 在foreach中修改列表的最佳方法是什么?
使用IEnumerable对象的条件是,当您使用Enumerable访问基础集合时,不得更改基础集合。 您可以推测Enumerable对象是原始集合的快照。 因此,如果您在枚举时尝试更改集合,将会抛出exception。 但是,Enumeration中的获取对象根本就不是一成不变的。
由于foreach循环中使用的variables是循环块的本地variables,因此该variables在块外部不可用。