Lazlo
Lazlo

Reputation: 8790

Why can struct change their own fields?

Consider the Foo struct as follows:

struct Foo
{
  public float X;
  public float Y;

  public Foo(float x, float y)
  {
    this.X = x;
    this.Y = y;
  }

  public void Change(float x)
  {
    this.X = x;
  }
}

I understand modifying the field in the constructor, that's perfectly logical to me and my understanding of structs as value, number-like immutable types.

However, since one can't do:

Foo bar = new Foo(1, 2);
bar.X = 5;

Why can one use:

Foo bar = new Foo(1, 2);
bar.Change(5);

EDIT: If structs are mutable, then why can't they be modified when in a list or returned from a property?

Cannot modify expression because it is not a variable

Upvotes: 6

Views: 7530

Answers (4)

supercat
supercat

Reputation: 81277

Structures in .net combine piecewise mutability with shallow-copy-on-assignment semantics as well as the ability to pass by value-assignment or by reference. There is no convention in .net, however, by which classes would be expected to expose properties by reference, nor do any .net language compilers provide a convenient means of doing so. It would be possible for a language to provide such a feature, with certain limitations, by recognizing that something like:

  somePoint.X = 5;

could be written as:

void SetXToFive(ref Point it) {it.X = 5;}
...
  SetXToFive(ref somePoint);

allowing the code which manipulates the Point (by setting its X field to 5) from the code which has access to it. If an object which would have a property of type Point then exposes a routine which accepts a delegate to a method like the above, code that wants to set field X of that property to 5 could pass that routine a delegate to SetXToFive, which the routine could then call with whatever storage location holds the Point in question.

Note that one advantage over such an approach, compared with simply exposing a reference to the thing to be manipulated, is that the owner of the Point would know when the code that was manipulating it had finished. Without some compiler supper, the approach would generally be more of a nuisance than a benefit, but with compiler support the semantics could be made much cleaner than would be possible via any other means.

Upvotes: 0

Eric Lippert
Eric Lippert

Reputation: 660463

Since one cannot do

Foo bar = new Foo(1, 2); 
bar.X = 5; 

Why can one use:

Foo bar = new Foo(1, 2); 
bar.Change(5); 

Your original question actually cannot be answered because it is predicated on a completely false assumption. Both code samples are perfectly legal, and so the question about why one is illegal is nonsensical. Let's move on to your follow-up question:

If structs are mutable, then why can't they be modified when in a list or returned from a property?

Because variables are mutable and values are immutable.

That's why they're called "variables", after all, because they can change.

When you say "bar.X = 5", "bar" is a local variable. Variables are allowed to change.

When you say "bar.Change(5)", "bar" is a local variable. Variables are allowed to change.

When you say "myArray[123].X = 5", "myArray[123]" is an array element and an array element is a variable. Variables are allowed to change.

When you say "myDictionary[123].X = 5", "myDictionary[123]" is not a variable. The value is returned from the dictionary, not a reference to the storage location. Since that is a value, not a variable, there is nothing there that can change, so the compiler does not allow it to change.

A subtle point is that when you attempt to change a field, the receiver must be a variable. If it is not a variable, it makes no sense; you are clearly attempting to mutate a variable and there's nothing there to mutate. When you call a method, the receiver must be a variable but what if you have a value? The method might not attempt to mutate anything, and so should be allowed to succeed. What the compiler actually does if the receiver of a method call on a struct is not a variable, then it makes a new temporary local variable and calls the method with that variable. So if you say: "myDictionary[123].Change(5);" that is the same as saying

var temp = myDictionary[123];
temp.Change(5);

Here "temp" is a variable, and the mutating method is allowed to change the temporary copy.

Is that now clear? The key takeaway here is variables can change.

Upvotes: 17

Default Writer
Default Writer

Reputation: 2566

In common, all C# structs are not immutable, even readonly ones. So you can't design your structs as immutable at all.

All structs are mutable, just like in C++ :)

Immutability means that data structures ate immutable at language level, that is not true for C#. I will show you how to break immutability rule using legal C# syntax, please note that NotReallyImmutableFoo.X is declared as a readonly field.

Cheers ;)

namespace test
{
    public unsafe struct MutableFoo
    {
        public int Id;
        public float X;
        public MutableFoo(int id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (MutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    public struct NotReallyImmutableFoo
    {
        public long Id;
        public readonly float X;
        public NotReallyImmutableFoo(long id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (NotReallyImmutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    // this calls breaks up the immutability rule, because we are modifying structures itself
    public static class MutabilityHelper
    {
        struct MutableFooPrototype
        {
            int Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        struct NotReallyImmutableFooPrototype
        {
            long Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        public static unsafe void Rewrite(NotReallyImmutableFoo* obj, float value)
        {
            NotReallyImmutableFooPrototype* p_obj = (NotReallyImmutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
        public static unsafe void Rewrite(MutableFoo* obj, float value)
        {
            MutableFooPrototype* p_obj = (MutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MutableFoo foo = new MutableFoo(0, 2);
            foo.X = 3; // X is writeable
            foo.Change(5); // write X using pointer prototyping

            NotReallyImmutableFoo nrifoo = new NotReallyImmutableFoo(0, 2);
            // error CS0191
            //nrifoo.X = 3; // X is not writeable
            nrifoo.Change(3); // anyway, write X using pointer prototyping
        }
    }
}

Upvotes: 1

Alastair Pitts
Alastair Pitts

Reputation: 19601

You've made a key mistaken assumption.

.NET structs are mutable. You can absolutely perform bar.X = 5;.

You should design structs to be immutable, but by the code you have provided, they are mutable.

Have a look at this question for a description of where mutable structs can get your into trouble. Immutability of structs

Upvotes: 9

Related Questions