Kal_Torak
Kal_Torak

Reputation: 2551

Unexpected behavior with Struct internal values

*Solved. Thanks for the explanations guys, I didn't fully understand the implications of using a value type in this situation.

I have a struct that I'm using from a static class. However, the behavior is showing unexpected behavior when I print it's internal state at runtime. Here's my struct:

    public struct VersionedObject
    {

        public VersionedObject(object o)
        {
            m_SelectedVer = 0;
            ObjectVersions = new List<object>();
            ObjectVersions.Add(o);
        }

        private int m_SelectedVer;
        public int SelectedVersion
        {
            get
            {
                return m_SelectedVer;
            }

        }

        public List<object> ObjectVersions;//Clarifying:  This is only used to retrieve values,  nothing is .Added from outside this struct in my code.

        public void AddObject(object m)
        {
            ObjectVersions.Add(m);
            m_SelectedVer = ObjectVersions.Count - 1;
        }
    }

Test code

        VersionedObject vo = new VersionedObject(1);
        vo.AddObject(2);//This is the second call to AddObject()
        //Expected value of vo.SelectedVerion:  1
        //Actual value of vo.SelectedVersion:   1

Now, if you test this code in isolation, i.e., copy it into your project to give it a whirl, it will return the expected result.

The problem; What I'm observing in my production code is this debug output:

objectName, ObjectVersions.Count:2, SelectedVer:0,

Why? From my understanding, and testing, this should be completely impossible under any circumstances.

My random guess is that there is some sort of immutability going on, that for some reason a new struct is being instanced via default constructor, and the ObjectVersions data is being copied over, but the m_SelectedVersion is private and cannot be copied into the new struct?
Does my use of Static classes and methods to manipulate the struct have anything to do with it?

I'm so stumped I'm just inventing wild guesses at this point.

Upvotes: 0

Views: 155

Answers (2)

Alexei Levenkov
Alexei Levenkov

Reputation: 100620

Struct is value type. So most likely you are creating multiple copies of your object in your actual code.

Consider simply changing struct to class as content of your struct is not really good fit for value type (as it is mutable and also contains mutable reference type).

More on "struct is value type":

First check FAQ which have many good answers already.

Value types are passed by value - so if you call function to update such object it will not update original. You can treat them similar to passing integer value to function: i.e. would you expect SomeFunction(42) to be able to change value of 42?

struct MyStruct { public int V;}
void UpdateStruct(MyStruct x)
{
  x.V = 42; // updates copy of passed in object, changes will not be visible outside.
}
....
var local = new MyStruct{V = 13}
UpdateStruct(local); // Hope to get local.V == 42
if (local.V == 13) {
  // Expected. copy inside UpdateStruct updated,
  // but this "local" is untouched.
}

Upvotes: 1

Nicholas Carey
Nicholas Carey

Reputation: 74355

Why is this a struct and not a class? Even better, why are you tracking the size of the backing store (List<T>) rather than letting the List<T> track that for you. Since that underlying backing store is public, it can be manipulated without your struct's knowledge. I suspect something in your production code is adding to the backing store without going through your struct.

If it were me, I'd set it up something like this, though I'd make it a class...but that's almost certainly a breaking change:

public struct VersionedObject
{

    public VersionedObject()
    {
        this.ObjectVersions = new List<object>() ;
        return ;
    }

    public VersionedObject(object o) : this()
    {
        ObjectVersions.Add(o);
        return ;
    }
    public VersionedObject( params object[] o ) : this()
    {
        ObjectVersions.AddRange( o ) ;
        return ;
    }

    public int SelectedVersion
    {
        get
        {
            int value = this.ObjectVersions.Count - 1 ;
            return value ;
        }
    }
    public List<object> ObjectVersions  ;

    public void AddObject(object m)
    {
        ObjectVersions.Add(m);
        return ;
    }

}

You'll note that this has the same semantics as your struct, but the SelectedVersion property now reflects what's actually in the backing store.

Upvotes: 1

Related Questions