14

In C#, if you have a struct like so:

struct Counter
{
    private int _count;

    public int Value
    {
        get { return _count; }
    }

    public int Increment()
    {
        return ++_count;
    }
}

And you have a program like so:

static readonly Counter counter = new Counter();

static void Main()
{
    // print the new value from the increment function
    Console.WriteLine(counter.Increment());
    // print off the value stored in the item
    Console.WriteLine(counter.Value);
}

The output of the program will be:

1
0

This seems completely wrong. I would either expect the output to be two 1s (as it is if Counter is a class or if struct Counter : ICounter and counter is an ICounter) or be a compilation error. I realize that detecting this at compilation time is a rather difficult matter, but this behavior seems to violate logic.

Is there a reason for this behavior beyond implementation difficulty?

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
  • 1
    *Why* would you expect two 1s? You said you wanted it to be readonly, so why do you want it to change? – Eric Lippert Oct 04 '10 at 23:19
  • @EricLippert - If I have a `readonly` array I can still elements of the array. This `readonly struct` seems counter-intuitive to me. It's seems like the `readonly` keyword is being pushed into the struct somehow. – Enigmativity May 03 '21 at 01:01
  • See Eric Lippert's ["Mutating Readonly Structs"](https://ericlippert.com/2008/05/14/mutating-readonly-structs/) – voidp Dec 10 '22 at 14:31

2 Answers2

8

structs are value types and therefore have a value type sematics. This means each time you access the struct you basically work with a copy of the struct's value.

In your sample you don't change the original struct but only a temporary copy of it.

See here for further explanations:

Why are mutable structs evil

Community
  • 1
  • 1
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • 6
    But if you take away the `readonly`, then the expected output of two 1s happens. So it clearly does not make a copy in that instance. Does this mean that all functions called on readonly structs create copies of the struct (since C# has no concept of a mutating function)? – Travis Gockel Oct 04 '10 at 22:23
  • 4
    Yes, that is the case according to section 7.5.4 of the C# language specification. See Eric Lippert's post on the topic to get more details. – Dirk Vollmar Oct 04 '10 at 22:29
  • 1
    One unfortunate design weakness in .net is that there is no means by which struct methods can indicate whether or not they will modify `this`. Since it would be annoying if one couldn't use any methods or properties on read-only structures, and it would also be annoying if `readonly` modifiers on structs weren't honored, C# and vb.net instead "compromise", by having method invocations on read-only structures make a copy of the struct before invoking the method. Note that if one exposes struct fields directly, the compiler will be able to distinguish read accesses from writes, and... – supercat Oct 08 '12 at 16:16
  • 1
    ...allow only the former on read-only structures. If one wishes to have a method modify a struct in-place, one should define it as a static method which takes the struct as a `ref` parameter since C# will not allow read-only structs to be passed by `ref` except as the implicit (problematic) `this` parameter. – supercat Oct 08 '12 at 16:19
4

In .net, a struct instance method is semantically equivalent to a static struct method with a an extra ref parameter of the struct type. Thus, given the declarations:

struct Blah { 
   public int value;
   public void Add(int Amount) { value += Amount; }
   public static void Add(ref Blah it; int Amount; it.value += Amount;}
}

The method calls:

someBlah.Add(5);
Blah.Add(ref someBlah, 5);

are semantically equivalent, except for one difference: the latter call will only be permitted if someBlah is a mutable storage location (variable, field, etc.) and not if it is a read-only storage location, or a temporary value (result of reading a property, etc.).

This faced the designers of .net languages with a problem: disallowing the use of any member functions on read-only structs would be annoying, but they didn't want to allow member functions to write to read-only variables. They decided to "punt", and make it so that calling an instance method on a read-only structure will make a copy of the structure, invoke the function on that, and then discard it. This has the effect of slowing down calls to instance methods which do not write the underlying struct, and making it so that an attempt to use a method which updates the underlying struct on a read-only struct will yield different broken semantics from what would be achieved if it were passed the struct directly. Note that the extra time taken by the copy will almost never yield correct semantics in cases which would not have been correct without the copy.

One of my major peeves in .net is that there is still (as of at least 4.0, and probably 4.5) still no attribute via which a struct member function can indicate whether it modifies this. People rail about how structs should be immutable, rather than providing the tools to allow structs to safely offer mutating methods. This, despite the fact that so-called "immutable" structs are a lie. All non-trivial value types in mutable storage locations are mutable, as are all boxed value types. Making a struct "immutable" may compel one to rewrite a whole struct when one only wants to change one field, but since struct1 = struct2 mutates struct1 by copying all the public and private fields from struct2, and there's nothing the type definition for the struct can do to prevent that (except not have any fields) it does nothing to prevent unexpected mutation of struct members. Further, because of threading issues, structs are very limited in their ability to enforce any sort of invariant relationship among their fields. IMHO, it would generally be better for a struct with to allow arbitrary field access, making clear that any code receiving a struct must check whether its fields meet all required conditions, than try to prevent the formation of structs which don't meet conditions.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • *`"struct1 = struct2` mutates struct1 by copying"* - that's true, but are there any practical problems with that (except that it's generally not atomic)? Both `struct1` and `struct2` are variables or fields filled with actual data, so copying is what should be expected in this case since this is how struct assignment works in C and C++ by default. Unless `struct1` is a `readonly` field, in which case it will fail at compile time. – vgru Nov 06 '15 at 16:16
  • Anyway, +1 for helpful information, but I would slightly rephrase the part where you say that *calling an instance method on a read-only structure will make a copy of the structure*, because it is that *accessing* a `readonly` struct field creates a copy of that struct. If you read the value into a local variable first, you can mutate it (as long as its inner fields are mutable) and .NET will operate on the stack based structure directly ("implementation details", I know). When a field is not `readonly`, then methods can operate on the field directly with no need to copy it. – vgru Nov 06 '15 at 16:25
  • @Groo: There aren't a whole lot of corner cases where it matters that `struct1=struct2` mutates the *existing* struct1 by overwriting all its fields, but the only way one can correctly understand those corner cases is by understanding what's actually happening. With regard to your second point, I'm not quite clear what you're suggesting; the phrase "read-only structure" refers both to variables that have a `readonly` qualifier, and also temporary storage locations such as function or property return values; do I need to note that the return values of even read/write properties are read-only? – supercat Nov 06 '15 at 17:45
  • No problem, it's clear, I wanted to say it's not the method call which causes the copy, but any access to the field, but it's clear enough I guess. – vgru Nov 06 '15 at 18:12
  • @Groo: If there exists a structure "readonly ExposedFieldStruct s;", I don't think the compiler will copy all of "s" if code needs to read a single field of it; it will, however, copy all of "s" if code needs to read a property of it (since properties are really methods in disguise). – supercat Nov 06 '15 at 20:34