Quantcast
Channel: : software-engineering
Viewing all articles
Browse latest Browse all 19

The Evil of Mutable Structs

$
0
0

I was fascinated by after watching Jon Skeet and Rob Conery discussing it on TekPub TV. The main takeaway is this: mutable structs are evil. I rewrote Jon Skeet’s example as an MSpec (Machine.Specifications) unit test just for fun, just to add my own flavour.

 

   1:using System.Collections.Generic;
   2:using Machine.Specifications;
   3:  
   4:internalstruct MutableStruct
   5: {
   6:publicint Value { get; set; }
   7:  
   8:publicvoid AssignValue(int newValue)
   9:     {
  10:         Value = newValue;
  11:     }
  12: }
  13:  
  14: [Subject("Mutable struct in foreach iterator")]
  15:internalclass SO13610559
  16: {
  17:static List<MutableStruct> list;
  18:  
  19:     Establish context = () => list = new List<MutableStruct>
  20:         {
  21:new MutableStruct {Value = 10}
  22:         };
  23:  
  24:     Because of = () =>
  25:         {
  26:foreach (MutableStruct item in list)
  27:             {
  28:                 item.AssignValue(30);
  29:// item.Value = 30;
  30:             }
  31:         };
  32:  
  33:     It should_equal_30 = () => list[0].Value.ShouldEqual(30);
  34: }

When the above specification is run, the output is:

image

The key to understanding the problem is to uncomment line 29 and see what the compiler thinks of that. When you directly assign to a member of item, the compiler says no, because it is read-only (foreach iterators are read-only by definition). Therefore, each time item is accessed, the compiler takes a fresh copy of it from the original source (you can verify this by decompiling the code with a tool such as JetBrains’ dotPeek, or ildasm). When item is changed as a side effect of calling a method, the compiler doesn’t detect that we’re writing to a read-only thing, so it just lets it happen, then silently discards the results, because we were working on a copy all along, not the original.

Interestingly, in line 4, changing struct to class makes the test pass. In that situation, we’re dealing with a reference type and it is the reference that's readonly, not the referenced object. When the compiler repeatedly gets a clean copy of the reference, that doesn’t stop us modifying the underlying value.


Viewing all articles
Browse latest Browse all 19

Trending Articles