2017-06-23 19 views
0

私の研究中、構造体は値型なので、コピーされた構造体の変更はクラスに対してではなくオリジナルを変更します。これは私が欲しい、期待している行動です。しかし、以下の最小コード例では、コピーされた構造体の変更が、リストと配列の両方についてオリジナルに反映されていることが明らかです。あなたがテストできるボタンが1つ付いたシンプルなWFAです。コピーされた構造体のフィールドの変更元の構造体フィールドの変更

どうしてですか? I readこれは、構造体を配列のメンバーとして渡すときに発生する可能性があります。これは、私がやっていない配列です。同様のトピックで見つかった他の質問は、この動作を示していません。重複または関連する質問がある場合、私はそれらを見つけられなかったし、今後それらを見つける方法のヒントに感謝します。

namespace TestingStructs 
{ 
public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    struct SingleLevel 
    { 
     public int Level { get; } 
     public int Property1 { get; } 
     public decimal Property2 { get; } 

     public SingleLevel(
      int level, 
      int prop1, 
      decimal prop2) 
     { 
      Property1 = prop1; 
      Property2 = prop2; 
      Level = level; 
     } 
    } 

    struct Levels 
    { 
     public int NumberOfLevels { get; } 
     public readonly List<SingleLevel> RightLevels; 
     public readonly SingleLevel[] RightLevelsArray; 
     public Levels(int numberofLevels) 
     { 
      NumberOfLevels = numberofLevels; 

      RightLevels = new List<SingleLevel>(); 
      RightLevelsArray = new SingleLevel[numberofLevels + 1]; 
      for (int i = 1; i <= 3; i++) 
      { 
       SingleLevel rightToAdd = new SingleLevel(i, i * 100, 10 + i); 
       RightLevels.Add(rightToAdd); 
       RightLevelsArray[i - 1] = rightToAdd; 
      } 
     } 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     Levels lastLevels = new Levels(3); 
     Levels temporaryLevels = lastLevels; 
     MessageBox.Show("temp"); 
     MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevels.Select(right => right.Property2))); 
     MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevelsArray.Select(right => right.Property2))); 
     MessageBox.Show("last"); 
     MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevels.Select(right => right.Property2))); 
     MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevelsArray.Select(right => right.Property2))); 

     SingleLevel rightLevelToAdd = new SingleLevel(1, 50, 9.5m); 
     temporaryLevels.RightLevels.Insert(0, rightLevelToAdd); 
     temporaryLevels.RightLevelsArray[0] = rightLevelToAdd; 

     MessageBox.Show("temp after insert"); 
     MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevels.Select(right => right.Property2))); 
     MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevelsArray.Select(right => right.Property2))); 
     MessageBox.Show("last after insert"); 
     MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevels.Select(right => right.Property2))); 
     MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevelsArray.Select(right => right.Property2))); 


    } 
    } 
} 

出力:挿入後のtempとlastは、同じ値のProperty2を含みます。

背景:私のコードでは、実行時に変更される場合とされない場合があるオブジェクトインスタンスを使用します。したがって、私はそれをコピーし、コピーを修正し、元のオブジェクトに戻したいと考えています。もしそうでなければ、私はオリジナルを変更して、オリジナルのステージの変更を適用したくないのです。デザインに欠陥があるかもしれませんが、質問は構造の問題に関するものです。これについてのヒントもありますが、私は感謝します。

答えて

2

temporaryLevelsは実際にはlastLevelsのコピーですが、プロパティRightLevelsとRightLevelsArrayは配列と同様に同じデータを参照し、リストはクラス型です。オブジェクト自体の割り当て

 Levels temporaryLevels = lastLevels; 

リストや配列への参照のコピーを作成するには

ではなく。

私はこれを行うには、任意の組み込みの方法を知っていませんが、レベルのコピーコンストラクタを定義することができます。

public Levels(Levels source) 
{ 
    NumberOfLevels = source.NumberOfLevels; 
    RightLevelsArray = new SingleLevel[NumberOfLevels + 1]; 
    source.RightLevels.CopyTo(RightLevelsArray); 
    RightLevels = new List<SingleLevel>(); 
    RightLevels.AddRange(source.RightLevels); 
} 

そして、代わりに割り当て、あなたはこのような新しいコンストラクタを呼び出します:

 Levels temporaryLevels = new Levels(lastLevels); 

今temporaryLevelsは、最後のレベルのディープコピーである、リストまたは1つの構造体の配列を変更すると、他は変更されません。

+0

Ok ..どうすれば変更できますか?自分の目的のためにリストとは異なるコレクションを使用しているとは思わないので、それらをコピーして個別に変更できる必要があります。何か提案がありますか? – pun11

+0

私は私の元の答えを編集し、可能な解決策を追加しました。 –

1

Iは、構造体は、このようにコピー 構造体の変化は、元

このない完全に真を変更しない、値型であることを読み取ります。構造体が含まれている場合、Structは実際には値型であり、スタックとして格納されます。

struct Foo 
{ 
    int A; 
    Bar bar; 
} 

struct Bar 
{ 
    int B; 
    int C; 
} 

sizeof(Foo) == sizeof(int)+sizeof(Bar) 

しかし、あなたの構造体が(例えば、あなたのクラスのために)、参照が含まれている場合にのみ自身を参照(アドレス値)をインライン化し、クラスではないこと参照が指されます。

struct Foo 
{ 
    int A; 
    Bar bar; 
} 

class Bar 
{ 
    int B; 
    int C; 
} 

sizeof(Foo) == sizeof(int)+sizeof(IntPtr) 

あなたのリストには、クラスです。そして、あなたの構造体には、この型のインスタンスへの参照だけが含まれています。インスタンス自体ではありません。

+0

あなたの答えは非常に洞察力があるようですが、私はこれが正直に何をしているのかわかりません..またはそれが私を助ける方法:)しかし、私は理解したい、あなたは精緻化していただけますか?ところで、C#では_ _Program.Foo 'はあらかじめ定義されたサイズを持たないため、sizeofは安全でないコンテキストでのみ使用できます(System.Runtime.InteropServices.Marshal.SizeOf_ – pun11

1

あなたの問題は、コードがどのように動作するかに関するいくつかの前提にあるようです。

あなたが行う場合は、この:

Levels temporaryLevels = lastLevels; 

それは、元の値のコピーを持つ新しいLevels構造体を作成します。コピーされるプロパティが構造体の場合、新しいバージョンが作成されます。クラスであれば、同じオブジェクトへの参照が挿入されます。つまり、2つのオブジェクトが異なるにもかかわらず配列とリストオブジェクトはどちらも同じクラスを参照するため、一方を変更すると他方が変更されます。

構造を自分で複製/コピーするコードを作成する必要がありますか?リストと配列に来ると、新しいものを作成し、古いものからのデータでデータを取り込む必要があります。データが構造体でない場合はコピーを作成します(この場合は構造体なので、その問題)。

+0

を使用することを検討してください) [List]の問題点としては、私の目的には他の選択肢はありませんが、自分の考え方を見ていますが、自分のクローン/コピー方法を書くときにどうすればこの問題に対処できますか? ? – pun11

+0

なぜあなたはリストだけでなく、配列(奇妙なデザインの選択のような感じ)が必要なのかはっきりしていませんが、それを扱うのは簡単です。コピーコードでは、 – Chris

+0

ああ、 'Array'は単に動作が同じかどうかをテストするためのもので、実際のコードでは配列は使用していません 提案していただきありがとうございます。ウィットhocronの答え:あなたのリストはクラスです。そして、あなたの構造体には、この型のインスタンスへの参照だけが含まれています。インスタンスそのものではありません。次のような声明を述べると、問題を正しく理解できますか? "既存の' List 'に新しい' List 'を作成した場合、私は参照を渡します(そして変更は元に変わります)。しかし、新しいリスト' List 'を作成すると、解決されましたか? – pun11

関連する問題