39

私が理解しているように、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;
    }
}

イテレータ i が不変であるため、C# の foreach で上記のことを行うことはできません。これは、言語における foreach の設計に固有のものだと思います (間違っている場合は訂正してください)。

foreach イテレータが不変である理由に興味があります。

4

6 に答える 6

26

ばかげているが説明的な例から始めましょう:

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

では、なぜMicrosoftは、反復に使用されるポインターの再割り当てを禁止しているのでしょうか。一つの明確さ-あなたは人々がループ内であなたの位置を変えたと思ってそれに割り当ててほしくない。別の実装の容易さ:変数は、進行中のループの状態を示す内部ロジックを非表示にする場合があります。

しかし、もっと重要なことは、あなたがそれに割り当てたいと思う理由がないということです。これは、ループシーケンスの現在の要素を表します。それに値を割り当てると、コーディングホラーに従う場合、「単一責任の原則」またはカーリーの法則に違反します。変数は1つのことだけを意味する必要があります。

于 2009-04-23T03:22:05.883 に答える
14

変数が変更可能であると、誤った印象を与える可能性があります。例えば:

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

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

配列の内容が変わると考える人もいるかもしれません。少し届いていますが、それが理由かもしれません。今夜、注釈付きの仕様で調べます...

于 2009-04-22T09:55:04.357 に答える
11

人為的な制限であり、実際に行う必要はないと思います。ポイントを証明するには、このコードを考慮に入れて、問題の解決策を考えてください。問題は割り当てることですが、オブジェクトの内部変更は問題になりません:

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

私はいつも説明した方法でオブジェクトを修正していたので、この現象について知りませんでした。

于 2009-04-22T11:35:54.817 に答える
4

コレクションを変更すると、変更によって予期しない副作用が発生する可能性があります。列挙子には、これらの副作用に正しく対処する方法を知る方法がないため、コレクションを不変にしました。

この質問も参照してください: foreachのリストを変更するための最良の方法は何ですか

于 2009-04-22T11:00:53.730 に答える
1

IEnumerable オブジェクトを操作するための条件は、Enumerable を使用してアクセスしている間、基になるコレクションを変更してはならないということです。Enumerable オブジェクトは元のコレクションのスナップ ショットであると推測できます。したがって、列挙中にコレクションを変更しようとすると、例外がスローされます。ただし、列挙型でフェッチされたオブジェクトはまったく不変ではありません。

foreach ループで使用される変数はループ ブロックに対してローカルであるため、この変数はブロックの外では使用できません。

于 2009-04-22T11:14:04.090 に答える
1

foreach ステートメントは Enumerable で機能します。Enumerable との違いは、コレクション全体を認識していないことです。ほとんど盲目的です。

これは、次に何が起こるか、または次の反復サイクルまで実際に何かがあるかどうかがわからないことを意味します。そのため、人々は Enumerable の数を取得できるように .ToList() がたくさん表示されます。

よくわからない何らかの理由で、これは、列挙子を移動しようとしているときにコレクションが変更されていないことを確認する必要があることを意味します。

于 2009-04-22T09:58:51.180 に答える