2009-04-22 6 views
33

私が理解しているように、C#のforeach反復変数は不変です。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 (int i = 0; i < Map.Count; i++) 
{ 
    Position Location = Map[i]; 
    Location = Location + Random(); 

    Plot(Location);   
    i = Location; 
} 

のために使用する必要があります:私はこのようなイテレータを変更することはできません意味

C++のバックグラウンドからは、forループの代わりにforeachが使用されています。しかし、上記の制限では、私は通常、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イテレータが不変であるのか興味があります。

+1

Good find。私はそれまで気づかなかったことに驚いています。 –

+2

2番目の例でも実際に配列要素を変更していません。 –

+0

@Jakob、私は本当に配列要素を変更することに興味がありません。私はイテレータがなぜ不変であるのかにもっと興味があります。私はそれを明確にするために質問を編集する必要があると思う。 – MrValdez

答えて

24

は愚かが、説明のための例から始めましょう:いいえ時点で

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

が、これはコンパイルした後、あなたの例ではLocationMapの値を参照するうち4.5が、その後、あなたは新しい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つのことについてはっきりしています - あなたは、ループ内であなたの位置を変更したと思っている人にそれを割り当てることを望まないでしょう。別の実装の容易さ:変数は、進行中のループの状態を示す内部ロジックを隠すかもしれません。

さらに重要なことに、にはを割り当てる必要はありません。これは、ループシーケンスの現在の要素を表します。 Coding Horrorに従うと、値に値を割り当てることで「Single Responsibility Principle」またはCurly's Lawが破損します。変数は1つのことだけを意味します。

+3

Tyler、私は間違っているかもしれませんが、私は、 "Postion Location"または "Object O"がイテレータであるというあなたの主張に同意しません。代わりに、イテレータを操作するイテレータを持つ反復変数です。それが事実なら、あなたの推論は少し歪んでいるかもしれないと私は思う。反復変数の値を変更すると明快な問題が発生することに同意しますが、マイクロソフトが開発者が決定するために残しておきたいと思います。私はforeach反復変数の値を変更すると便利だったいくつかのインスタンスを見つけました。 –

+0

@AnthonyGatlin私は、この場合、開発者に決定させるべきだと私は同意しません。コードを混乱させるには非常にコストがかかります。マイクロソフトでは、C#でコーディングする人が十分にいます。できるだけ多くの人がバグを作成するのを防ぐ言語を作成することに非常に大きなインセンティブがあります。 – tylerl

+0

"*変数は1つのものだけを意味します*"。良いコンセプト。しかし、私の頭では、 "* reference *"や "* pointer *"ではなく、単に "* current element *"を意味する方が良いでしょう。あるいは、opのインスタンス 'Map [i]'で、これは私たちが直感的に期待しているものです。 – cregox

12

変数が可変であれば、誤った印象を与える可能性があります。たとえば、次のように

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

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

それは配列の内容を変更するだろうと思うかもしれません一部の人々。それは少しに達していますが、それは理由かもしれません。私は今夜​​私の注釈付き仕様でそれを見ていきます...

+1

それはジョーンが私が推測するように描こうとしているポイントになるだろう。 "If"に注目してください。 –

+1

ブライアン、今削除された別のコメントに対するあなたのコメントはありましたか? –

+3

あなたはこれをスペックで見たことがありますか?私はデザイナーがあなたが示唆しているように明瞭にするために読んだだけの価値を作ったと思っていますが、他の考慮事項があるかどうかを知ることは面白いでしょう! –

2

foreach文はEnumerableで動作します。 Enumerableとは異なる点は、コレクション全体がわからないということです。ほとんど目隠しです。

これは、次に何が来るのか、実際に次の繰り返しサイクルまで何かがあるかどうかを知らないことを意味します。それでなぜあなたが.ToList()をたくさん見れば、人々はEnumerableの数をつかむことができるのですか?

わかりませんが、これは、列挙子を移動しようとすると、コレクションが変更されていないことを保証する必要があることを意味します。

+2

しかし*変数を変更しても、列挙子は変更されません。現在の値のローカルコピーです。 –

+1

確かにそれはその参照または値の型に依存しますか?しかし、ええ、私はあなたが値を見ていると理解しているかもしれません、そしておそらく列挙子自体を調整していないかもしれません... hmm – Ian

+0

確かに、これは反復子変数の変更を許可しない "難しい"理由ではありません。コンパイラはすでに 'foreach'ループを許可するかどうかを決めるために' IEnumerable'/'IEnumerable 'を探すために既にハードコードされています。 *言語設計者が反復変数の変更を許可するように選択した場合、 'IList' /' IList 'を使って' foreach'ループを反復処理する場合、コンパイラはそのような変更を受け入れることができました。 –

2

コレクションを変更すると、予期しない副作用が発生することがあります。列挙子は、これらの副作用を正しく処理する方法を知らないため、コレクションを不変にしました。 What is the best way to modify a list in a foreach

+0

列挙子が処理しなければならない副作用の具体的な例を少なくとも1つ挙げることができますか?(反復変数が不変の場合は発生しません)それ以外の場合、この回答は極めて曖昧に聞こえます。 –

1

のIEnumerableオブジェクトを操作するための条件あなたは可算でそれにアクセスしている間、基になるコレクションを変更してはならないということです。

も、この質問を参照してください。 Enumerableオブジェクトは元のコレクションのスナップショットであると推測できます。したがって、列挙中にコレクションを変更しようとすると、例外がスローされます。しかし、Enumerationのフェッチされたオブジェクトは、まったく変更できません。

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

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

私は常にオブジェクト私が説明した方法を変更したため、私は、この現象については知りませんでした。

+2

+1まともな回避策です。 – ashes999