web-dev-qa-db-ja.com

アクセサで完全に変更できるのに、なぜforeach反復変数を割り当てることができないのですか?

これに興味がありました。foreach反復変数を変更できないため、次のコードはコンパイルできません。

        foreach (var item in MyObjectList)
        {
            item = Value;
        }

ただし、次のコードはコンパイルおよび実行されます。

        foreach (var item in MyObjectList)
        {
            item.Value = Value;
        }

なぜ最初のものは無効なのに、2番目のものは下で同じことができます(このために正しい英語表現を探していましたが、覚えていません。

46
GianT971

foreachは、IEnumerableを実装するクラスを動的に反復する読み取り専用のイテレーターです。foreachの各サイクルはIEnumerableを呼び出して次のアイテムを取得します。所有しているアイテムは読み取り専用の参照です。再割り当てはできませんが、item.Valueはそれにアクセスし、読み取り/書き込み属性に値を割り当てていますが、それでもアイテムの参照は読み取り専用です。

35
Mohamed Abed

2番目はまったく同じことをしていません。 item変数の値を変更するのではなく、その値が参照するオブジェクトのプロパティを変更します。 itemが可変値型の場合、これら2つはonlyと同等になります。この場合、可変値型としてとにかくそれを変更する必要があります悪です。 (これらは、不注意な開発者が予期しないかもしれないあらゆる種類の方法で動作します。)

これと同じです:

private readonly StringBuilder builder = new StringBuilder();

// Later...
builder = null; // Not allowed - you can't change the *variable*

// Allowed - changes the contents of the *object* to which the value
// of builder refers.
builder.Append("Foo");

詳細については、私の 参照と値に関する記事 を参照してください。

33
Jon Skeet

列挙されている間、コレクションを変更することはできません。 2番目の例では、オブジェクトのプロパティのみを更新しますが、これはまったく異なります。

コレクション内の要素を追加/削除/変更する必要がある場合は、forループを使用します。

for (int i = 0; i < MyObjectList.Count; i++)
{
    MyObjectList[i] = new MyObject();
}
19
James Johnson

言語仕様を見ると、これが機能しない理由がわかります。

仕様では、foreachは次のコードに展開されると述べています。

 E e = ((C)(x)).GetEnumerator();
   try {
      V v;
      while (e.MoveNext()) {
         v = (V)(T)e.Current;
                  embedded-statement
      }
   }
   finally {
      … // Dispose e
   }

ご覧のとおり、現在の要素はMoveNext()の呼び出しに使用されます。したがって、現在の要素を変更すると、コードは「失われ」、コレクションを反復処理できなくなります。そのため、コンパイラが実際に生成しているコードを見ると、要素を別のものに変更しても意味がありません。

11
Wouter de Kort

woulditemを可変にすることは可能です。コードの生成方法を次のように変更できます。

foreach (var item in MyObjectList)
{
  item = Value;
}

以下と同等になりました:

using(var enumerator = MyObjectList.GetEnumerator())
{
  while(enumerator.MoveNext())
  {
    var item = enumerator.Current;
    item = Value;
  }
}

そして、コンパイルされます。ただし、コレクションには影響しません。

そして摩擦があります。コード:

foreach (var item in MyObjectList)
{
  item = Value;
}

人間がそれについて考えるための2つの合理的な方法があります。 1つは、itemは単なるプレースホルダーであり、それを変更してもitemを変更するのと同じことです。

for(int item = 0; item < 100; item++)
    item *= 2; //perfectly valid

もう1つは、アイテムを変更すると実際にコレクションが変更されることです。

前者の場合、アイテムを別の変数に割り当てて、それで遊ぶことができるので、損失はありません。後者の場合、これは両方とも禁止されています(または、少なくともexpectを使用してコレクションを繰り返し処理することはできませんが、 すべての列挙子によって強制されるがあり、多くの場合、提供することは不可能です(列挙可能なものの性質によって異なります)。

前者のケースを「正しい」実装とみなしたとしても、人間が2つの異なる方法で合理的に解釈できるという事実は、それを許可しないようにするのに十分な理由です。 。

4
Jon Hanna

基本的に、最初のものはあまり意味をなさないからです。変数項目は反復子によって制御されます(反復ごとに設定されます)。変更する必要はありません。別の変数を使用するだけです。

foreach (var item in MyObjectList)
{
    var someOtherItem = Value;

    ....
}

2番目については、有効なユースケースがあります。車の列挙を反復処理し、各車で.Drive()を呼び出すか、car.gasTank = full;

2
Chris Shain

ポイントは、繰り返し処理中にコレクション自体を変更できないことです。イテレータが生成するオブジェクトを変更することは絶対に合法であり、一般的です。

1

2つの同じではないであるため。最初の操作を行うとき、コレクション内の値を変更することを期待できます。しかし、foreachが機能する方法では、実行できる方法はありません。コレクションからアイテムを取得することはできますが、設定することはできません。

ただし、アイテムを取得すると、他のアイテムと同様のオブジェクトになり、任意の(許可された)方法で変更できます。

1
svick

Foreachループの代わりにforループを使用し、値を割り当てます。それが動作します

foreach (var a in FixValue)
{
    for (int i = 0; i < str.Count(); i++)
    {
       if (a.Value.Contains(str[i]))
           str[i] = a.Key;
    }
 }
1
SPnL