I'm continuing my study of C# and the language specification and Here goes another behavior that I don't quite understand:

The C# Language Specification clearly states the following in section 10.4:

The type specified in a constant declaration must be sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum-type, or a reference-type.

It also states in section 4.1.4 the following:

Through const declarations it is possible to declare constants of the simple types (§10.4). It is not possible to have constants of other struct types, but a similar effect is provided by static readonly fields.

Ok, so a similar effect can be gained by using static readonly. Reading this I went and tried the following code:

static void Main()
    Console.Write("Hit a key to exit...");

static Point staticPoint = new Point(0, 0);
static readonly Point staticReadOnlyPoint = new Point(0, 0);

public static void OffsetPoints()
    staticPoint.Offset(1, 1);
    staticReadOnlyPoint.Offset(1, 1);

static void PrintOutPoints()
    Console.WriteLine("Static Point: X={0};Y={1}", staticPoint.X, staticPoint.Y);
    Console.WriteLine("Static readonly Point: X={0};Y={1}", staticReadOnlyPoint.X, staticReadOnlyPoint.Y);

The output of this code is:

Static Point: X=0;Y=0

Static readonly Point: X=0;Y=0


Static Point: X=1;Y=1

Static readonly Point: X=0;Y=0

Hit a key to exit...

I really expected the compiler to give me some kind of warning about mutating a static readonly field or failing that, to mutate the field as it would with a reference type.

I know mutable value types are evil (why did Microsoft ever implement Point as mutable is a mystery) but shouldn't the compiler warn you in some way that you are trying to mutate a static readonly value type? Or at least warn you that your Offset() method will not have the "desired" side effects?

share|improve this question
I dont see where you are mutating a 'static readonly field'. I suspect you are misunderstanding the concept. – leppie Jan 24 '12 at 9:50
Mutable structs are evil! – vc 74 Jan 24 '12 at 9:54
This is quite interesting. I did not expect the output to be different. Both in fact should be mutated, but I see the same results as you. Weird... – leppie Jan 24 '12 at 9:55
My idea for this issue is to focus on the usage of the types const and readonly (const is known at design time, and readonly is not). This might give you a better idea why there is no compiler response (as known readonly be reassigned). – Saturn Technologies Jan 24 '12 at 9:55
@leppie Yes, and that's one of the reasons why SO is awesome, it allows you to learn new things every day and often things you cannot find in books. Sometimes the answer comes from the gurus (Lippert, Skeet and the likes) but sometimes the solution is constructed by all the people who answer a question and it is really great :) – vc 74 Jan 24 '12 at 11:46
up vote 10 down vote accepted

Eric Lippert explains what's going on here:

...if the field is readonly and the reference occurs outside an instance constructor of the class in which the field is declared, then the result is a value, namely the value of the field I in the object referenced by E.

The important word here is that the result is the value of the field, not the variable associated with the field. Readonly fields are not variables outside of the constructor. (The initializer here is considered to be inside the constructor; see my earlier post on that subject.)

Oh and just to stress on the evilness of mutable structs, here is his conclusion:

This is yet another reason why mutable value types are evil. Try to always make value types immutable.

share|improve this answer
Beat me to it! I was about to post it. – Igal Tabachnik Jan 24 '12 at 10:07
Accepted as it was the first post to point out how value typed readonly fields work. Thanks! – InBetween Jan 24 '12 at 11:01
@InBetween, you're most welcome. Thanks for posting an interesting question :) – vc 74 Jan 24 '12 at 11:47

The point of the readonly is that you cannot reassign the reference or value.

In other words if you attempted this

staticReadOnlyPoint = new Point(1, 1);

you would get a compiler error because you are attempting to reassign staticReadOnlyPoint. The compiler will prevent you from doing this.

However, readonly doesn't enforce whether the value or referenced object itself is mutable - that is a behaviour that is designed into the class or struct by the person creating it.

[EDIT: to properly address the odd behaviour being described]

The reason you see the behaviour where staticReadOnlyPoint appears to be immutable is not because it is immutable itself, but because it is a readonly struct. This means that every time you access it, you are taking a full copy of it.

So your line

staticReadOnlyPoint.Offset(1, 1);

is accessing, and mutating, a copy of the field, not the actual value in the field. When you subsequently write out the value you are then writing out yet another copy of the original (not the mutated copy).

The copy you did mutate with the call to Offset is discarded, because it is never assigned to anything.

share|improve this answer
That still does not explain the (unexpected) output of the code. – leppie Jan 24 '12 at 9:56
@Precisely! But the fact remains, given the output of the code, that the field is in fact immutable. – InBetween Jan 24 '12 at 9:59

If you look at the IL, you will see that on usage of the readonly field, a copy is made before calling Offset:

IL_0014: ldsfld valuetype [System.Drawing]System.Drawing.Point 
IL_0019: stloc.0
IL_001a: ldloca.s CS$0$0000

Why this is happening, is beyond me.

It could be part of the spec, or a compiler bug (but it looks a bit too intentional for the latter).

share|improve this answer

The effect is due to several well-defined features coming together.

readonly means that the field in question cannot be changed, but not that the target of the field cannot be changed. This is more easily understood (and more often useful in practice) with readonly fields of a mutable reference type, where you can do x.SomeMutatingMethod() but not x = someNewObject.

So, first item is; you can mutate the target of a readonly field.

Second item is, that when you access a non-variable value type you obtain a copy of the value. The least confusing example of this is giveMeAPoint().Offset(1, 1) because there isn't a known location for us to later observe that the value-type returned by giveMeAPoint() may or may not have been mutated.

This is why value types are not evil, but are in some ways worse. Truly evil code doesn't have a well-defined behaviour, and all of this is well-defined. It's still confusing though (confusing enough for me to get this wrong on my first answer), and confusing is worse than evil when you're trying to code. Easily understood evil is so much more easily avoided.

share|improve this answer
I dont agree with the 1st one's behavior. The IL does not support that semantics. SomeMethod is simply called on the original (myValueType). – leppie Jan 24 '12 at 10:13
@leppie That's precisely what I had in mind when I talked about eliding the copying. The observed behaviour is the same either way, so why add extra work for no gain. My understanding is that the rule is that value types must be accessed as if they were value-based types (ref of a field does bypass though), but not that code must be emitted that would have no effect but to slow things down (and possibly introduce sheared reads). – Jon Hanna Jan 24 '12 at 10:33
Second item is, that when you access a value type you obtain a copy of the value. Hence both calls to Offset obtain a copy of this.whatever local to the calling method, and call Offset on that. That is not entirely true or I'm misunderstanding you. It is true for readonly fields as it turns out, but not for value types in general as Offset(int, int) does mutate the value type if it is not defined as readonly – InBetween Jan 24 '12 at 10:36
@InBetween That doesn't contradict, a (conceptual, no need for it to actually be what the computer does) copy - change - copy sequence would mutate the non-readonly field. – Jon Hanna Jan 24 '12 at 10:40
No, it occurs to me that @leppie is correct. Will edit... – Jon Hanna Jan 24 '12 at 13:45

The compiler simply doesn't have enough information available about a method to know that the method mutates the struct. A method may well have a side-effect that's useful but doesn't otherwise change any members of the struct. If would technically be possible to add such analysis to the compiler. But that won't work for any types that live in another assembly.

The missing ingredient is a metadata token that indicates that a method doesn't mutate any members. Like the const keyword in C++. Not available. It would have be drastically non-CLS compliant if it was added in the original design. There are very few languages that support the notion. I can only think of C++ but I don't get out much.

Fwiw, the compiler does generate explicit code to ensure that the statement cannot accidentally modify the readonly. This statement

staticReadOnlyPoint.Offset(1, 1);

gets translated to

Point temp = staticReadOnlyPoint;   // makes a copy
temp.Offset(1, 1);

Adding code that then compares the value and generates a runtime error is also only technically possible. It costs too much.

share|improve this answer
+1: Nice explanation. I still find this behavior misleading to be honest but there isn't any good solution to the problem as it currently stands. Maybe the best solution would have been to consider some mutable keyword for value types when C# 1.0 was designed. – InBetween Jan 24 '12 at 10:32
It is still very non-CLS compliant to this day. – Hans Passant Jan 24 '12 at 10:44
The fact that allowing member functions to indicate whether this should be passed by ref, const-ref, or value would be "non-CLS compliant" does not mean it would be a bad idea--merely that the CLS has a severe deficiency. – supercat Jan 24 '12 at 17:25

The observed behavior is an unfortunate consequence of the fact that neither the Framework nor C# provides any means by which member function declarations can specify whether this should be passed by ref, const-ref, or value. Instead, value types always pass this by (non-const-restricted) ref, and reference types always pass this by value.

The 'proper' behavior for a compiler would be to forbid passing immutable or temporary values by non-const-restricted ref. If such restriction could be imposed, ensuring proper semantics for mutable value types would mean following a simple rule: if you make an implicit copy of a struct, you're doing something wrong. Unfortunately, the fact that member functions can only accept this by non-const-restricted ref means a language designer must make one of three choices:

  1. Guess that a member function won't modify `this`, and simply pass immutable or temporary variables by `ref`. This would be most efficient for functions which do not, in fact, modify `this`, but could dangerously expose to modification things that should be immutable.
  2. Don't allow member functions to be used on immutable or temporary entities. This would avoid improper semantics, but would be a really annoying restriction, especially given that most member functions do not modify `this`.
  3. Allow the use of member functions except those deemed most likely to modify `this` (e.g. property setters), but instead of passing immutable entities directly by ref, copy them to temporary locations and pass those.

Microsoft's choice protects constants from improper modification, but has the unfortunate consequences that code will run needlessly slowly when calling functions that don't modify this, while generally working incorrectly for those which do.

Given the way this is actually handled, one's best bet is to avoid making any changes to it in structure member functions other than property setters. Having property setters or mutable fields is fine, since the compiler will correctly forbid any attempt to use property setters on immutable or temporary objects, or to modify any fields thereof.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.