web-dev-qa-db-ja.com

C#では、foreachループで値型インスタンスのメンバーを変更できないのはなぜですか?

値型は不変でなければならないことを知っていますが、それは単なる提案であり、規則ではありませんか?それで、なぜ私はこのようなことをすることができません:

struct MyStruct
{
    public string Name { get; set; }
}

 public class Program
{
    static void Main(string[] args)
    {
        MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } };
        foreach (var item in array)
        {
            item.Name = "3";
        }
        //for (int i = 0; i < array.Length; i++)
        //{
        //    array[i].Name = "3";
        //}

        Console.ReadLine();
    }
}

コード内のforeachループはコンパイルされませんが、コメント化されたforループは正常に機能します。エラーメッセージ:

「foreach反復変数」であるため、「item」のメンバーを変更できません

何故ですか?

59

Foreachは列挙子を使用し、列挙子は基になるコレクションを変更できないため、canが、参照するオブジェクトを参照するコレクション内のオブジェクト。ここで、Value型とReference型のセマンティクスが機能します。

参照型、つまりクラスでは、すべてのコレクションが格納しているのはオブジェクトへの参照です。そのため、オブジェクトのメンバーに実際に触れることはなく、あまり気にすることはありません。オブジェクトを変更しても、コレクションには影響しません。

一方、値型はその構造全体をコレクションに保存します。コレクションを変更して列挙子を無効にしない限り、そのメンバーに触れることはできません。

さらに、列挙子はコレクション内の値のコピーa copyを返します。 ref型では、これは何も意味しません。参照のコピーは同じ参照になります。また、範囲外に広がる変更を使用して、任意の方法で参照オブジェクトを変更できます。一方、値型では、オブジェクトのコピーのみが取得されるため、そのコピーの変更は反映されません。

63
Kyte

それはあなたがそれを侵害するのを止めるものは何もないという意味での提案ですが、実際には「提案」よりもはるかに大きな重みが与えられるべきです。例えば、あなたがここで見ている理由のために。

値型は、実際の値を参照ではなく変数に保存します。つまり、配列に値があり、参照ではなく、itemにその値のcopyがあります。 itemの値を変更できる場合、それは完全に新しい値であるため、配列の値には反映されません。これが許可されない理由です。

これを行うには、インデックスで配列をループし、一時変数を使用しないでください。

16
Adam Robinson

構造は値型です。
クラスは参照型です。

ForEach構成体は、IEnumerableデータ型要素のIEnumeratorを使用します。その場合、変数は読み取り専用であり、変更できないという意味です。また、値タイプがあるため、同じメモリを共有しているため、含まれる値を変更できません。

C#lang spec section 8.8.4:

反復変数は、埋め込みステートメントを拡張するスコープを持つ読み取り専用ローカル変数に対応します

これを修正するには、構造を考慮したクラスを使用します。

class MyStruct {
    public string Name { get; set; }
}

編集:@CuiPengFei

var暗黙的な型を使用する場合、コンパイラーがあなたを支援するのは難しくなります。 MyStructを使用すると、構造の場合は読み取り専用であることがわかります。クラスの場合、アイテムへの参照は読み取り専用であるため、item = null;ループ内ですが、そのプロパティを変更できます。これは変更可能です。

を使用することもできます(structを使用する場合):

        MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } };
        for (int index=0; index < array.Length; index++)
        {
            var item = array[index];
            item.Name = "3";
        }
9
Margus

注:アダムのコメントによると、これは実際には問題の正しい答え/原因ではありません。しかし、まだ心に留めておく価値があるものです。

[〜#〜] msdn [〜#〜] から。 Enumeratorを使用する場合、値を変更することはできません。これは、基本的にforeachが実行していることです。

列挙子を使用してコレクション内のデータを読み取ることはできますが、基になるコレクションを変更するために使用することはできません。

コレクションが変更されない限り、列挙子は有効なままです。要素の追加、変更、削除など、コレクションに変更が加えられた場合、列挙子は回復不能に無効になり、その動作は未定義になります。

3
Ian

値のタイプは value by called です。つまり、変数を評価すると、そのコピーが作成されます。これが許可された場合でも、MyStructの元のインスタンスではなく、コピーを編集することになります。

foreachはC#では読み取り専用です 。参照タイプ(クラス)の場合、参照のみが読み取り専用であるため、これはあまり変わりません。したがって、次の操作を行うことができます。

    MyClass[] array = new MyClass[] {
        new MyClass { Name = "1" },
        new MyClass { Name = "2" }
    };
    foreach ( var item in array )
    {
        item.Name = "3";
    }

ただし、値型(構造体)の場合、オブジェクト全体が読み取り専用であるため、発生しているものが発生します。 foreachでオブジェクトを調整することはできません。

1
Steven Jeuris

MyStructを(構造体の代わりに)クラスにすると、それが可能になります。

1
Adrian Carneiro

私はあなたが下のアンサーを見つけることができると思います。

Foreach struct C#の奇妙なコンパイルエラー

1
Jethro