Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are good uses for mutable structs?

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?

like image 436
Jouke van der Maas Avatar asked Apr 21 '11 18:04

Jouke van der Maas


3 Answers

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

like image 119
Marc Gravell Avatar answered Oct 13 '22 06:10

Marc Gravell


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.

like image 38
Matt Ellen Avatar answered Oct 13 '22 06:10

Matt Ellen


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.

like image 42
supercat Avatar answered Oct 13 '22 06:10

supercat