6

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

Community
  • 1
  • 1
Lazlo
  • 8,518
  • 14
  • 77
  • 116
  • 2
    Your first statement works for me. Can you actually reproduce that in some framework, or is it just an assumption? – Noon Silk Jan 18 '11 at 04:28
  • Assigning values to public fields is perfectly fine. Just think about it - you should be able to change their values somehow :) – Elalfer Jan 18 '11 at 04:32
  • 2
    Structs aren't immutable. There is nothing in the language the forces them to be. It is true that most struct types are immutable, but that is up the implementor of the struct to enforce, by not providing methods like Change() and not proving access to fields or settings or properties. You example struct Foo is mutable. – shf301 Jan 18 '11 at 04:35
  • Edited my question, added a more specific footer. – Lazlo Jan 18 '11 at 04:37
  • Semantics, semantics, semantics! – leppie Jan 18 '11 at 04:50
  • Possible explanation here: http://stackoverflow.com/questions/608542/immutability-of-structs/608559#608559 – Alan Turing Oct 06 '11 at 01:42

4 Answers4

15

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    I had already understood that from the answers preceding yours, but thanks nonetheless. +1 for thorough explanation. – Lazlo Jan 18 '11 at 18:13
8

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

Community
  • 1
  • 1
Alastair Pitts
  • 19,423
  • 9
  • 68
  • 97
2

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
        }
    }
}
Alan Turing
  • 2,482
  • 17
  • 20
  • 1
    _unsafe_ code is exactly for breaking through high-level C# code to the level of low-level implementation. In run-time world we have to encode our state in some memory/registers/other stuff, because we usually interested in state transition and some external interaction. Since we still need freedom to use constructed/computed fields even if they are immutable we have to expose them through mutable memory. But I'm pretty sure, that strings are loaded/mapped (from file) into read-only memory area to bring some optimization like sharing between applications, or absence of page invalidation etc. – ony Aug 01 '11 at 22:16
  • @ony: Like your vision, but this post is mostly the fun, just to show that there are always some rules to break, and stay thinking originally. – Alan Turing Oct 05 '11 at 22:14
  • 2
    Yes, you are *"using legal C# syntax"*, but you are cheating quite a bit with `unsafe` code there. You could have also used Reflection as "legal C# syntax" to [mutate an immutable **class**](http://stackoverflow.com/a/934942/69809). Claiming that *you can't design your structs as immutable at all* is misleading. – vgru Aug 30 '13 at 09:00
  • Ok, this will goo.gl/hX8tRP – Alan Turing Sep 20 '18 at 18:38
0

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.

supercat
  • 77,689
  • 9
  • 166
  • 211