为什么 C# foreach 语句中的迭代变量是只读的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/776430/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 00:42:50  来源:igfitidea点击:

Why is The Iteration Variable in a C# foreach statement read-only?

c#language-design

提问by MrValdez

As I understand it, C#'s foreach iteration variable is immutable.

据我了解,C# 的 foreach 迭代变量是不可变的。

Which means I can't modify the iterator like this:

这意味着我不能像这样修改迭代器:

foreach (Position Location in Map)
{
     //We want to fudge the position to hide the exact coordinates
     Location = Location + Random();     //Compiler Error

     Plot(Location);
}

I can't modify the iterator variable directly and instead, I have to use a for loop

我不能直接修改迭代器变量,而是必须使用 for 循环

for (int i = 0; i < Map.Count; i++)
{
     Position Location = Map[i];
     Location = Location + Random();

     Plot(Location);        
     i = Location;
}

Coming from a C++ background, I see foreach as an alternative to the for loop. But with the above restriction, I usually fallback to using the for loop.

来自 C++ 背景,我认为 foreach 可以替代 for 循环。但是由于上述限制,我通常会回退到使用 for 循环。

I'm curious, what is the rationale behind making the iterator immutable?

我很好奇,使迭代器不可变背后的基本原理是什么?



Edit:

编辑:

This question is more of a curiousity question and not as a coding question. I appreciated the coding answers but I can't mark them as answers.

这个问题更像是一个好奇的问题,而不是一个编码问题。我很欣赏编码答案,但我无法将它们标记为答案。

Also, the example above was over-simplified. Here is a C++ example of what I want to do:

此外,上面的示例过于简化了。这是我想要做的一个 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;
    }
}

I can't do the above in C#'s foreach because the iterator i is immutable. I think (correct me if I'm wrong), this is specific to the design of foreach in languages.

我无法在 C# 的 foreach 中执行上述操作,因为迭代器 i 是不可变的。我认为(如果我错了,请纠正我),这是特定于语言中 foreach 的设计的。

I'm interested in why the foreach iterator is immutable.

我对为什么 foreach 迭代器是不可变的感兴趣。

采纳答案by tylerl

Lets start out with a silly but illustrative example:

让我们从一个愚蠢但说明性的例子开始:

Object o = 15;
o = "apples";

At no point do we get the impression that we just turned the number 15 into a string of apples. We know that ois simply a pointer. Now lets do this in iterator form.

我们绝不会觉得我们只是将数字 15 变成了一串苹果。我们知道这o只是一个指针。现在让我们以迭代器的形式来做这件事。

int[] nums = { 15, 16, 17 };

foreach (Object o in nums) {
     o = "apples";
}

Again, this really accomplishes nothing. Or at least it wouldaccomplish nothing were it to compile. It certainly wouldn't insert our string into the int array -- that's not allowed, and we know that ois just a pointer anyway.

再说一次,这真的什么也做不了。或者至少它不会完成任何编译。它当然不会将我们的字符串插入到 int 数组中——这是不允许的,而且我们知道o无论如何这只是一个指针。

Let's take your example:

让我们以你的例子为例:

foreach (Position Location in Map)
{
     //We want to fudge the position to hide the exact coordinates
     Location = Location + Random();     //Compiler Error

     Plot(Location);
}

Were this to compile, the Locationin your example stars out referring to a value in Map, but then you change it to refer to a new Position(implicitly created by the addition operator). Functionally it's equivalent to this (which DOES compile):

如果要编译,则Location在您的示例中,您引用了一个值 in Map,但随后您将其更改为引用一个 new 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);
}

So, why does Microsoft prohibit you from re-assigning the pointer used for iteration? Clarity for one thing -- you don't want people assigning to it thinking they've changed your position within the loop. Ease of implementation for another: The variable might hide some internal logic indicating the state of the loop in progress.

那么,为什么 Microsoft 禁止您重新分配用于迭代的指针?澄清一件事——你不希望人们分配给它,认为他们已经改变了你在循环中的位置。另一个易于实现:变量可能隐藏一些内部逻辑,指示正在进行的循环状态。

But more importantly, there is no reason for you to wantto assign to it. It represents the current element of the looping sequence. Assigning a value to it breaks the "Single Responsibility Principle" or Curly's Lawif you follow Coding Horror. A variable should mean one thing only.

但更重要的是,您没有理由想要分配给它。它代表循环序列的当前元素。如果您遵循 Coding Horror ,则为其分配一个值违反了“单一职责原则”或Curly 定律。一个变量应该只意味着一件事。

回答by Jon Skeet

If the variable were mutable, that might give an incorrect impression. For example:

如果变量是可变的,那可能会给人不正确的印象。例如:

string[] names = { "Jon", "Holly", "Tom", "Robin", "William" };

foreach (string name in names)
{
    name = name + " Skeet";
}

Some people mightthink that would change the array contents. It's reaching a bit, but it might be a reason. I'll look it up in my annotated spec tonight...

有些人可能认为这会改变数组内容。它达到了一点,但这可能是一个原因。今晚我会在我的注释规范中查找它...

回答by Ian

The foreach statement works with Enumerable. The thing that is different with an Enumerable is that it doesn't know about the collection as a whole, it's almost blind.

foreach 语句适用于 Enumerable。Enumerable 的不同之处在于它不知道整个集合,它几乎是盲目的。

This means that it doesn't know what's coming next, or indeed if there is anything until the next iteration cycle. That why there you see .ToList() a lot so people can grab a count of the Enumerable.

这意味着它不知道接下来会发生什么,或者在下一个迭代周期之前是否真的有任何事情发生。这就是为什么您会经常看到 .ToList() 以便人们可以获取 Enumerable 的数量。

For some reason that I'm not sure about, this means that you need to ensure your collection isn't changing as you try to move the enumerator along.

出于某种我不确定的原因,这意味着您需要确保您的集合在您尝试移动枚举器时不会发生变化。

回答by Rik

When you modify the collection, the modification might have unpredictable side effects. The enumerator has no way of knowing how to deal correctly with these side effects, so they made the collections immutable.

当您修改集合时,修改可能会产生不可预测的副作用。枚举器无法知道如何正确处理这些副作用,因此它们使集合不可变。

See also this question: What is the best way to modify a list in a foreach

另请参阅此问题: What is the best way to modify a list in a foreach

回答by S M Kamran

The condition to work with IEnumerable objects is that the underlying collection must not change while you are accessing it with Enumerable. You can presume that the Enumerable object is a snap shot of the original collection. So if you tries to change the collection while enumerating it'll throw an exception. However the fetched objects in Enumeration is not immutable at all.

使用 IEnumerable 对象的条件是当您使用 Enumerable 访问基础集合时不得更改它。您可以假定 Enumerable 对象是原始集合的快照。因此,如果您在枚举时尝试更改集合,它将引发异常。然而,枚举中获取的对象根本不是一成不变的。

Since the variable used in foreach loop is local to the loop block this variable is however not available outside the block.

由于 foreach 循环中使用的变量是循环块的本地变量,因此该变量在块外不可用。

回答by majkinetor

I think its artificial limitation, no really need to do it. To prove the point, take this code into considiration, and a possible solution to your problem. The problem is to asign, but internal changes of objects don't present a problem:

我认为这是人为的限制,没有必要这样做。为了证明这一点,请考虑此代码以及解决问题的可能方法。问题是分配,但对象的内部更改不存在问题:

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;
        }
    }
}

I didn't know about this phenomena, because I was always modifying the objects the way I described.

我不知道这种现象,因为我总是按照我描述的方式修改对象。