C# and immutability and readonly fields... a lie?

I have found that People claim that using all readonly fields in a class does not necessarily make that class's instance immutable because there are "ways" to change the readonly field values even after initialization (construction).

How? What ways?

So my question is when can we really have a "real" immutable object in C#, that I can safely use in threading?

Also do anonymous types create immutable objects? And some say LINQ uses immutable objecst internally. How exactly?


Solution 1:

You've asked like five questions in there. I'll answer the first one:

Having all readonly fields in a class does not necessarily make that class's instance immutable because there are "ways" to change the readonly field values even after construction. How?

Is it possible to change a readonly field after construction?

Yes, if you are sufficiently trusted to break the rules of read-only-ness.

How does that work?

Every bit of user memory in your process is mutable. Conventions like readonly fields might make certain bits appear to be immutable, but if you try hard enough, you can mutate them. For example, you can take an immutable object instance, obtain its address, and change the raw bits directly. Doing so might require a great deal of cleverness and knowledge of the internal implementation details of the memory manager, but somehow the memory manager manages to mutate that memory, so you can too if you try hard enough. You can also use "private reflection" to break various parts of the safety system if you are sufficiently trusted.

By definition, fully trusted code is allowed to break the rules of the safety system. That's what "fully trusted" means. If your fully trusted code chooses to use tools like private reflection or unsafe code to break the memory safety rules, fully trusted code is allowed to do that.

Please don't. Doing so is dangerous and confusing. The memory safety system is designed to make it easier to reason about the correctness of your code; deliberately violating its rules is a bad idea.

So, is "readonly" a lie? Well, suppose I told you that if everyone obeys the rules, everyone gets one slice of cake. Is the cake a lie? That claim is not the claim "you will get a slice of cake". That's the claim that if everyone obeys the rules, you'll get a slice of cake. If someone cheats and takes your slice, no cake for you.

Is a readonly field of a class readonly? Yes but only if everyone obeys the rules. So, readonly fields are not "a lie". The contract is, if everyone obeys the rules of the system then the field is observed to be readonly. If someone breaks the rules, then maybe it isn't. That doesn't make the statement "if everyone obeys the rules, the field is readonly" a lie!

A question you did not ask, but perhaps should have, is whether "readonly" on fields of a struct is a "lie" as well. See Does using public readonly fields for immutable structs work? for some thoughts on that question. Readonly fields on a struct are much more of a lie than readonly fields on a class.

As for the rest of your questions -- I think you'll get better results if you ask one question per question, rather than five questions per question.

Solution 2:

EDIT: I've concentrated on code working "within" the system as opposed to using reflection etc as Eric mentions.

It depends on the type of the field. If the field itself is of an immutable type (e.g. String) then that's fine. If it's StringBuilder then the object can appear to change even though the fields themselves don't change their values, because the "embedded" objects can change.

Anonymous types are exactly the same. For example:

var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }

So, basically if you have a type that you want to be properly immutable, you need to make sure it only refers to immutable data.

There's also the matter of structs which can have readonly fields but still expose methods which mutate themselves by reassigning this. Such structs behave somewhat strangely depending on the exact situation in which you call them. Not nice - don't do it.

Eric Lippert has written a great deal about immutability - it's all gold, as you'd expect... go read :) (I hadn't noticed that Eric was writing an answer to this question when I wrote this bit. Obviously read his answer too!)