74

構造体のリストがあり、1 つの要素を変更したいと考えています。例えば ​​:

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

ここで、1 つの要素を変更したいと思います。

MyList[1].Name = "bob"

ただし、これを試みるたびに、次のエラーが発生します。

System.Collections.Generic.List.this[int]' の戻り値は変数ではないため変更できません

クラスのリストを使用すると、問題は発生しません。

答えは、構造体が値型であることに関係していると思います。

したがって、構造体のリストがある場合、それらを読み取り専用として扱う必要がありますか? リスト内の要素を変更する必要がある場合、構造体ではなくクラスを使用する必要がありますか?

4

6 に答える 6

50

そうではありません。型をクラスまたは構造体として設計することは、コレクションに格納する必要性によって駆動されるべきではありません:)必要な「セマンティクス」を確認する必要があります

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

Struct obItem = MyList[1];

構造体の新しいインスタンスが作成され、すべてのメンバーが 1 つずつコピーされます。MyList[1] のクローン、つまり 2 つのインスタンスがあるようにします。obItem を変更しても、オリジナルには影響しません。

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

ここで2分間我慢してください(これは飲み込むのに時間がかかります..私にとってはそうでした:)構造体をコレクションに格納し、質問で示したように変更する必要がある場合は、作成する必要がありますあなたの構造体はインターフェースを公開します(ただし、これはボクシングになります)。次に、ボックス化されたオブジェクトを参照するインターフェイス参照を介して、実際の構造体を変更できます。

次のコード スニペットは、上で述べたことを示しています。

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-私がそんなに簡単なものを見落としているかどうかを確認するために走ってもらいました。(セッタープロパティが存在しない場合、メソッドが行われた場合は一貫性がありません - .NETユニバースはまだバランスが取れています:)
セッターメソッドは機能しません
[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);
}
于 2008-09-09T10:56:06.587 に答える
43
MyList[1] = new MyStruct("bob");

C# の構造体は、ほとんどの場合、不変になるように設計する必要があります (つまり、一度作成すると内部状態を変更する方法がありません)。

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

于 2008-09-09T10:27:56.700 に答える
16

構造体が「不変」というわけではありません。

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

Andrew が述べているように、構造体全体を置き換える必要があります。その点として、そもそも(クラスではなく)構造体を使用している理由を自問する必要があると思います。時期尚早の最適化の問題を回避して実行していないことを確認してください。

于 2008-09-09T22:22:50.867 に答える
7

フィールドを公開している構造体、またはプロパティセッターを介してミューテーションを許可している構造体には何の問題もありません。ただし、メソッドまたはプロパティゲッターに応答して自身を変更する構造体は、システムが一時的な構造体インスタンスでメソッドまたはプロパティゲッターを呼び出すことを許可するため、危険です。メソッドまたはゲッターが構造体に変更を加えた場合、それらの変更は最終的に破棄されます。

残念ながら、ご存知のように、.netに組み込まれているコレクションは、そこに含まれている値型オブジェクトを公開するのに非常に弱いです。通常、最善の策は次のようなことを行うことです。

  MyStruct temp = myList [1];
  temp.Name="アルバート";
  myList [1] = temp;

やや面倒で、スレッドセーフではありません。同じことを行うには次のようなクラスタイプのリストよりもさらに改善されています。

  myList [1] .Name = "Albert";

ただし、次のものも必要になる場合があります。

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

または多分

  myClass temp =(myClass)myList [1] .Clone();
  temp.Name="アルバート";
  myList [1] = temp;

または多分他のバリエーション。myClassと、リストに追加された他のコードを調べない限り、実際には知ることができません。アクセス権のないアセンブリ内のコードを調べないと、最初のフォームが安全かどうかを知ることができない可能性があります。対照的に、NameがMyStructの公開フィールドである場合、MyStructに他に何が含まれているか、またはコードが実行される前にmyListで他に何が行われたか、またはコードが何を期待するかに関係なく、名前を更新するために指定したメソッドは機能します。後でそれを行います。

于 2011-12-19T07:59:41.553 に答える