2008-09-09 15 views
53

構造体のリストがあり、1つの要素を変更したいとします。私が使用している場合は構造体のリスト内の要素の値を変更する

Cannot modify the return value of System.Collections.Generic.List.this[int]‘ because it is not a variable

:私がしようとするたびに、しかし

MyList[1].Name = "bob" 

と、私は次のエラーを取得する次の操作を行います。

MyList.Add(new MyStruct("john"); 
MyList.Add(new MyStruct("peter"); 

今私は1つの要素を変更する例:クラスのリスト、問題は発生しません。

答えは、値型である構造体と関係があります。

私は構造体のリストを持っていれば、として扱うべきですか??リスト内の要素を変更する必要がある場合は、クラスを使用し、構造体は使用しないでください。

答えて

28
MyList[1] = new MyStruct("bob"); 

C#の構造体は、ほとんど常に不変(つまり、作成された後は内部状態を変更する方法がない)に設計する必要があります。

あなたの場合、1つのプロパティまたはフィールドだけを変更しようとするのではなく、指定された配列インデックスで構造体全体を置き換えることをお勧めします。

+2

これは完全な答えではありません、紀州の答えははるかに完全です。 – Motti

+0

Jolsonが言ったこと - 構造体が「不変」であることはあまりありません。正しい。 -1 cos構造体が不変であると言うのは本当に間違っています。 – GuruC

+2

Andrewにとって公正であること - 私は、structが "不変"であると言っていると解釈していません。すべてのフィールドが読み込み専用であれば、それらを不変にすることができます。 – Montdidier

38

型をクラスまたは構造体として設計することは、コレクションに格納する必要性によって引き起こされるべきではありません:)必要な意味を見てください。

表示される問題は、値型セマンティクスによるものです。各値型の変数/参照は新しいインスタンスです。あなたが言うとき

Struct obItem = MyList[1]; 

何が起こるかは、構造体の新しいインスタンスが作成され、すべてのメンバーが1つずつコピーされるということです。 MyList [1]、つまり2つのインスタンスのクローンを持つようにします。 これで、obItemを変更しても、オリジナルには影響しません。今では私のためにした。これは、飲み込むにはしばらくかかります(ここでは2分間私と一緒にクマ:) をあなたが本当に構造体は、コレクションに保存されていると、あなたの中で示されたように変更する必要がある場合は

obItem.Name = "Gishu"; // MyList[1].Name still remains "peter" 

質問、あなたは構造体を公開する必要があります(しかし、これはボクシングになります)。実際の構造体を、ボックス化されたオブジェクトを参照するインタフェース参照を介して変更することができます。

次のコードスニペットは、私はちょうど

public interface IMyStructModifier 
{  String Name { set;  }  } 
public struct MyStruct : IMyStructModifier ... 

List<Object> obList = new List<object>(); 
obList.Add(new MyStruct("ABC")); 
obList.Add(new MyStruct("DEF")); 

MyStruct temp = (MyStruct)obList[1]; 
temp.Name = "Gishu"; 
foreach (MyStruct s in obList) // => "ABC", "DEF" 
{  Console.WriteLine(s.Name);   } 

IMyStructModifier temp2 = obList[1] as IMyStructModifier; 
temp2.Name = "Now Gishu"; 
foreach (MyStruct s in obList) // => "ABC", "Now Gishu" 
{  Console.WriteLine(s.Name);  } 

HTHの上に言ったことを示しています。良い質問。
更新: @Hath - 私は単純なことを見落としているかどうかを確認するために走っていました。 (setterのプロパティがなく、メソッドがしていれば、.Netの宇宙はまだバランスがとれています。)
セッターメソッドが機能しません。
obList2 [1]は状態が変更されるコピーを返します。リストの元の構造体は変更されません。 Set-via-Interfaceはそれを行う唯一の方法だと思われます。

List<MyStruct> obList2 = new List<MyStruct>(); 
obList2.Add(new MyStruct("ABC")); 
obList2.Add(new MyStruct("DEF")); 
obList2[1].SetName("WTH"); 
foreach (MyStruct s in obList2) // => "ABC", "DEF" 
{ 
    Console.WriteLine(s.Name); 
} 
+0

まだ良くありません。リストはインターフェースタイプとして宣言されなければなりません。その場合、その中のすべての項目はボックス化されます。すべての値型に対して、クラス型セマンティクス*を持つボックス化された同等*があります。クラスを使用する場合は、クラスを使用しますが、それ以外の厄介な警告に注意してください。 – supercat

+0

@Supercat - 私は上記のように...それはボクシングの原因になります。私はインターフェイスの参照を介して変更を推奨していません - ちょうどあなたがコレクションを持っている必要がある場合に動作すると言っているstructs +は、インプレースを変更する。基本的には、値型のref-typeラッパーを作っています。 – Gishu

+1

'List 'を使うのではなく、structがセッターインターフェースを実装するのではなく(上記のようにセマンティクスがひどいものです)、代わりに 'class SimpleHolder {public T Value; } 'を使用し、' List > 'を使用します。 'Value'が構造体ではなくフィールドである場合、' obList2 [1] .Value.Name = "George"; 'のような文は正しく動作します。 – supercat

11

構造体が「不変」であることはあまりありません。

実際の根本的な問題は、構造体が参照型ではなくValue型であることです。したがって、リストから構造体への「参照」を引き出すと、構造体全体の新しいコピーが作成されます。したがって、あなたがそれを変更すると、リストの元のバージョンではなくコピーが変更されます。

Andrew州のように、構造体全体を置き換える必要があります。その点として、私はあなたがなぜクラスの代わりに構造体を最初に使用しているのか自分に尋ねなければならないと思います。早すぎる最適化の問題を回避していないことを確認してください。

4

フィールドが公開されているか、プロパティセットターを介して突然変異を許可する構造体には何も問題ありません。しかし、メソッドやプロパティゲッターに応答して自分自身を変更する構造体は、システムが一時構造体インスタンスに対してメソッドまたはプロパティゲッターを呼び出すことができるため、危険です。メソッドやゲッターが構造体を変更すると、それらの変更は破棄されてしまいます。

残念ながら、ネットに組み込まれているコレクションは、その中に含まれている値型オブジェクトを公開するにあたり実際には脆弱です。あなたの最善の策は、通常次のようなことです:

 
    MyStruct temp = myList[1]; 
    temp.Name = "Albert"; 
    myList[1] = temp; 

やや厄介で、スレッドセーフではありません。それでも同じことをやってする必要がありますクラス型の一覧を超える改善、:

 
    myList[1].Name = "Albert"; 

が、それはまた、必要な場合があります:

 
    myList[1] = myList[1].Withname("Albert"); 

または多分

 
    myClass temp = (myClass)myList[1].Clone(); 
    temp.Name = "Albert"; 
    myList[1] = temp; 

または多分いくつかの他のバリエーション。実際には、myClassと他のコードを調べてリストに入れない限り、誰も知ることができません。誰もアクセスできないアセンブリ内のコードを調べなくても、最初のフォームが安全かどうかを知ることができない可能性があります。対照的に、NameがMyStructの公開されたフィールドであれば、MyStructに含まれているものに関係なく、あるいはコードが実行される前にmyListで何が行われたかに関係なく、後にそれを行う。

+0

あなたは "名前が' MyStruct'の公開されたフィールドであれば、私がそれを更新するために与えたメソッドは... "正確ではありません。参照の 'class'の場合にスレッドセーフの幽霊を提起したので、' ValueType'コードを同じ基準で判断することは公正であり、リスト内のインデックスの位置は 'myList [1 ] 'はもはやフェッチした' struct'インスタンスには対応しません。これを修正するには、コレクションインスタンス全体を認識している何らかのロックや同期が必要です。そして、 'class'バージョンはまだこれらの同じ問題に苦しんでいます。 –

+0

@GlennSlayden:インプレース編集のためにbyrefsとしてアイテムを公開するコレクションは、すべての追加や削除が行われた場合に、参照が公開される前にアイテムがスレッドセーフな方法で簡単に編集できるようにします。必要であれば、バイパスに影響を与えずに最後にアイテムを追加できるようにコレクションを構築できますが、新しいオブジェクトや配列を追加するだけで拡張が可能です。 – supercat

+0

私はそれが不可能ではなかったとは言わなかったし、それを修正するための多くの方法を確かに知っていただろう、私はちょうどあなたの例で競争状態を指摘したい。おそらくそれは、この不明瞭な地域の私の興味深いミクロ専門知識が南極の氷河の隕石のように突き出るようになるからかもしれません。 「インプレース編集のためのバイパスとして項目を公開するコレクションは、「はい、まさにそうです。私は自分自身でそれをもっと良く言ったことはありませんでした。そこに行って、それをして、素晴らしい作品。ああ、私はほとんど、ロックフリーの並行性も忘れていた。 –

関連する問題