pun11
pun11

Reputation: 157

Modification of a field in copied structure change original structure field

During my research I read that structs are value types, thus the change of a copied struct does not change the original, as opposed to classes. This is the behavior I want and expect. However, in the following minimum code example, it is clear that change in the copied struct reflects back to the original - both for lists and arrays. It is a simple WFA with one button for you to test.

Why is this happening? I read that this may happen when passing structs as members of arrays, which I am not doing, other questions I found on similar topics do not show this behavior. If there are any duplicates or related questions, I did not find them and will be grateful for tips how to find them in the future.

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)));


    }
  }
}

Output: temp and last after insert contains the very same values of Property2.

Background: In my code, I use an object instance which may or may not be modified during the runtime. Thus, I want to copy it, modify the copy and then potentially equal it back to the original object. If not, I do not want to change the original and thus not apply the modifications of any stage to the original. The design may be flawed, the question is about the struct issue. If you have any tips on this as well, though, I will be grateful.

Upvotes: 0

Views: 461

Answers (3)

Helmut D
Helmut D

Reputation: 680

Although temporaryLevels is actually a copy of lastLevels, the properties RightLevels and RightLevelsArray still refer to the same data, as arrays and lists are class types.

In the assignment

       Levels temporaryLevels = lastLevels;

you create a copy of the references to the list and the array, but not of the objects themselves.

I don't know of any built-in way to do this, but you can define a copying constructor in Levels:

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

And, instead of the assignment, you call the new constructor like this:

        Levels temporaryLevels = new Levels(lastLevels);

Now temporaryLevels is a deep copy of last Levels, and modifying the list or array of one struct won't change the other.

Upvotes: 2

Chris
Chris

Reputation: 27599

Your problem seems to be in some assumptions you have made about how the code works.

When you do this:

Levels temporaryLevels = lastLevels;

It will create a new Levels struct with copies of the values on the original. If the property being copied is a struct then it will create a new version, if it is a class then it will put a reference to the same object in. This means that although your two objects are different the array and list objects in them are both referring to the same classes so changing one changes the other.

To do what you want you would need to create code to clone/copy the structures yourself. When you come to the List and array you would need to create new ones and populate them with data from the old ones, ensuring that you create copies if the data is not structs (though in this case they are structs so you don't have that issue).

Upvotes: 1

eocron
eocron

Reputation: 7526

I read that structs are value types, thus the change of a copied struct does not change the original

This not completely true. Struct is indeed value type and stored as one, if it contains struct - it will still inline fields in stack, yes.

struct Foo
{
    int A;
    Bar bar;
}

struct Bar
{
    int B;
    int C;
}

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

BUT, if your struct contains references, (for example, for your classes) it will inline only reference itself (address value), not class it reference is pointed to.

struct Foo
{
    int A;
    Bar bar;
}

class Bar
{
    int B;
    int C;
}

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

Your List is a class. And your struct contains only reference to instance of this type. Not instance itself.

Upvotes: 1

Related Questions