12

(Note: This sample code requires C# 7.2 or later, and the Nuget System.Memory package.)

Let's suppose we have a readonly struct as follows:

public readonly struct Test
{
    public Test(int value)
    {
        Value = value;
    }

    public int Value { get; }
}

Now let's put it into an array:

var array = new Test[] { new Test(1) };

Console.WriteLine(array[0].Value); // Prints 1

So far so good. You cannot write code to modify array[0].Value directly.

Now suppose we do this:

array.AsSpan().AsBytes()[3] = 1;

Console.WriteLine(array[0].Value); // Prints 16777217

So now we've modified the Value component of the readonly struct in the array.

Is this behaviour correct?

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    @Will Not sure if it's actually a bug, which is why I'm asking. It might be expected behaviour! – Matthew Watson Dec 12 '17 at 15:11
  • 3
    *You cannot write code to modify array[0].Value directly.* I think that's all about it. Couldn't you also modify it using Reflection? Span seems to be just another, *indirect* method in this case.. – adjan Dec 12 '17 at 15:19
  • @downvoter: It would be helpful to indicate what you think is wrong with this question, so that it can be improved. – Matthew Watson Dec 12 '17 at 15:35
  • 1
    C# is a total crap if we trying to talk about immutability, or protecting data from unexpected modifications without extreme overhead like "full clone". It is really sad for me that C# architects ignore this concept completely. – Eugene Jun 04 '18 at 14:04

1 Answers1

19

Is this behaviour correct?

Yes. A readonly struct does not change the mutability of the variable which holds a copy of the struct! Array elements are variables and variables can vary.

You don't need to use C# 7.2 to see this. Integers are immutable; there's no way to turn the integer 3 into the integer 4. Rather, you replace the contents of a variable containing 3 with 4. The fact that integers are immutable doesn't make variables into constants. Same here. The struct is immutable, just like an int, but the variable holding it is mutable.

Similarly, a readonly field on a struct is a lie; that field can be observed to change because structs do not own their storage. See Does using public readonly fields for immutable structs work? for more on this.

(And of course everything is mutable if you break the rules of the language and runtime by using reflection at a high trust level or unsafe code.)

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • To be able to replace content of variable with new value, you should be able to obtain that new value somehow in the first place. What if struct constructor have some limits on values it will create: `public readonly struct DivisibleBy256 { public DivisibleBy256(int value) { Value = value & ~255; } public int Value { get; } }`, will `.AsSpan().AsBytes()` respect this limits? – user4003407 Dec 12 '17 at 16:09
  • 4
    @PetSerAl: Nope. Writing bytes directly to memory is one of the most dangerous things you can do. If you don't like what happens when you do so, then my advice is: don't do it. – Eric Lippert Dec 12 '17 at 16:35
  • 1
    My main concern was: how to prevent others from doing so. But on closer inspection, it seems that whole `System.Memory` assembly is implicitly `SecurityCritical`, due to absence of any assembly level security attributes, thus it is not callable from partially trusted code. – user4003407 Dec 12 '17 at 17:19
  • 1
    @PetSerAl: C# is only *mostly* memory safe and type safe. The core language is pretty good at enabling the vast majority of programmers to never violate safety, but there are plenty of opportunities for fully trusted code to do so if they really want to; this is just one more. – Eric Lippert Dec 12 '17 at 17:33