6

So I know that mutable structs/value types are considered 'evil' in .Net. So why is it possible to make them? What are some good uses for mutable structs that justify adding the feature to the CLR in the fist place?

Jouke van der Maas
  • 4,117
  • 2
  • 28
  • 35

3 Answers3

3

An excellent question! Personally, I'm definitely not a fan, however some people running on things like CF/XNA will swear blind that they need the extra bit of performance they can eek from (over)using structs, which generally involves them being mutable. This argument has some value if the struct is accessed directly in an array, on a public field, or accessed via ref, but in many cases you might get bitten by chomping through stack-space by duplicating an over-sized struct many times on the stack.

If you are using structs, mutable-or-not, and if you are using serialization, then a fully immutable struct can be a real problem. Partial or "popsicle" immutability can be an answer here, but the same frameworks that are tempting for value-types also tend to have more reflection restrictions, making private/field-based serialization tricky. So mutable is handy.

All of the above are really examples of using a struct to hold something that is actually better classified as an object. Pure values don't change. Ever. So full immutability is fine.

As a final thought... not a justification for the existence, but it makes excellent interview fodder, and is great for tripping up the unwary. Maybe it was designed by people acting as consultants ;p

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I just changed the code I was working on to use mutable classes where I had mutable structs, ran the single-threaded set of unit tests a few times (the multi-threaded set has too little consistency in timings for this to tell me anything) and found the worse run of the struct-based to be 10% more performant than the best of the reference-based, for the entire set (that is, including code that doesn't even use those objects). The averages are around 12-13% from each other, which is about what Knuth had in his optimisation in his famously quoted paper :) Not a type I'll expose, so worth it, easy – Jon Hanna Jan 02 '12 at 19:49
  • Though yeah, it also helps that the exposed classes that use these structs implement ISerializable so serialisation doesn't have to touch them directly at all. – Jon Hanna Jan 02 '12 at 19:50
2

Partially mutable structs are good for making builders for those structs.

So if you have something like:

namespace StructEx
{
    public struct AStruct
    {
        public int AnInt { get; internal set; }
    }

    public class AStructBuilder
    {
        public AStruct BuildStruct(int anInt)
        {
            return new AStruct { AnInt = anInt };
        }
    }
}

In one assembly, and then anything outside that assembly cannot modify the struct you build, but building them is easy.

Matt Ellen
  • 11,268
  • 4
  • 68
  • 90
  • 1
    You could argue that on the public API that is an immutable struct, and that `AStruct` could also be written with a constructor to set `readonly` fields (and this wouldn't change the API) - but I see what you are sayingl. – Marc Gravell Apr 21 '11 at 18:54
  • I was going to ask why not just have the parameter in the struct constructor then I remembered that you can't get rid of the default constructor for structs. +1 for a legitimate use. – Davy8 Apr 21 '11 at 18:55
  • 1
    Actually what I just said makes no sense because it's still possible to create an empty `AStruct` – Davy8 Apr 21 '11 at 18:57
  • Yeah, this toy example isn't the best. I was thinking along the lines of `StringBuilder`, but couldn't quite come up with an interesting thing to build. I agree, I use `readonly` and initialise from the constructor for my structs. I've not used the above pattern. – Matt Ellen Apr 21 '11 at 19:04
2

Mutable structs are the only kind of data type in .net which can implement mutable value semantics. Some people like Eric Lippert hate them because they make life more complicated for compiler writers, but the semantics they offer are frequently much clearer than those available with reference types.

For example, suppose one has as class and a struct type, and an interface as follows:

struct myStruct {public int v; ... other stuff...};
class myClass {public int v; ... other stuff...};
interface ISample {
  void useStructByValue(myStruct z);
  void useStructByReference(ref myStruct z);
  void useClassByValue(myClass z);
  void useClassByReference(ref myClass z);
}

and consider the following method:

void test(myStruct struct1, myStruct struct2, myClass class1, myClass class2,
  ISample munger)
{
  for(int i=0; i < 5; i++)
  {
    munger.useStructByValue(struct1);         // S1
    munger.useStructByReference(ref struct2); // S2
    munger.useClassByvalue(class1);           // S3
    munger.useClassByReference(ref class2);   // S4
  }
}

Assuming only that munger does not use any unsafe code, which of the passed-in items may have their .v field affected by which of the four statements? I would suggest that even without seeing the entire definition of struct1, or any portion of the implementation of munger, one can tell that struct1.v will not be changed by any of them. Guaranteed. Further, struct2.v may be changed by S2, but not by any of the other statements. Again, guaranteed. The values of class1.v and class2.v, however, may be changed by any of the statements; the only way to know which of the statements could change class1.v or class2.v would be to examine the code for every single type that will ever, now or in the future, implement ISample.

In other words, structs offer semantics that are limited but well-defined. Classes don't.

Incidentally, because of limitations in how properties work, one cannot directly modify, nor pass by reference, fields of struct properties. Even so, code like:

List<myStruct> myList;
...
myStruct tempStruct = myList[1];
tempStrict.v = 5;
myList[1] = tempStruct;

while not thread-safe, has clear and obvious semantics. Substitute myClass for myStruct, and certainty goes out the window. If each item of the list has an unshared instance of myClass, one could simply say myList[1].v = 5;, nicely and conveniently. Unfortunately, determining whether a list item has an unshared instance of myClass is almost impossible. If someone, trying to copy the values in myList[1] to myList[0] had said something like myList[0] = myList[1];, such a statement would have worked, but would cause a subsequent write to myList[1].v to also affect myList[0].v. Nasty nasty nasty. Structs wouldn't have that problem.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • In your last paragraph, first line, you reversed myStruct and myClass. – Jouke van der Maas Dec 20 '11 at 18:32
  • @JoukevanderMaas: My point in the last paragraph was to say that while the code required to change a field of a struct exposed as a property is more cumbersome than ideal, its semantics are self-explanatory, unlike those of classes; the last paragraph talks about the problems which classes have and structs don't. A struct with two integer fields holds two integers. A class with two integer fields holds two integers, *plus* another critical but non-retrievable thing: the set of all references to it. The semantics of a class type may be greatly altered by the existence of... – supercat Dec 21 '11 at 00:22
  • ...references to it, but there's no general way of knowing whether or where such references might exist unless an object instantiates another object itself and never exposes a reference anywhere. If one needs to store the relationships among objects, this inter-connectedness can be useful. If what one wants, however, is simply to store data, the inter-connectedness is just a burdensome complication. – supercat Dec 21 '11 at 00:24