C# and immutability and readonly fields… a lie?

2019-01-17 02:21发布

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?

5条回答
劫难
2楼-- · 2019-01-17 02:58

Readonly and Thread Safety are two different notions as Eric Lippert explains it.

查看更多
三岁会撩人
3楼-- · 2019-01-17 02:58

Perhaps the "People" had heard 3rd hand and communicated poorly about the special privilege that the constructor gets with regards to "readonly" fields. They are not readonly.

Not only that, they're not single-assignment. The constructor for a class can modify the field as many times as it pleases. All without breaking any "rules".

Constructors can also call other arbitrary methods, passing themselves as parameters. So if you're that other method, you can't be certain whether that field is immutable yet, because maybe you were invoked by that object's constructor, and the constructor is going to modify the field as soon as you're done.

You can try it yourself:

using System;
class App
{
    class Foo 
    {
        readonly int x;
        public Foo() {  x = 1; Frob(); x = 2; Frob(); }
        void Frob() { Console.WriteLine(x); }
    }
    static void Main()
    {
        new Foo();
    }
}

This program prints 1, 2. 'x' is modified after Frob read it.

Now, within any single method body, the value has to be constant -- the constructor cannot delegate modify-access to other methods or Delegates, so until a method returns, I'm pretty sure the field will need to remain stable.

All of the above is about classes. Structs are another story entirely.

查看更多
Evening l夕情丶
4楼-- · 2019-01-17 03:04

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.

查看更多
等我变得足够好
5楼-- · 2019-01-17 03:08

I think Eric's answer is far beyond the scope of the original question, to the point of not even answering it, so I'll take a stab at it:

What is readonly? Well, if we're talking about value types, it's simple: Once the value type is initialized and given a value, it can never change (at least as far as the compiler is concerned).

The confusion starts to set in when we talk about using readonly with reference types. It's at this point we need to distinguish the two components of a reference type:

  • The "reference" (variable, pointer) which points to the address in memory where your object lives
  • The memory that contains the data the reference is pointing to

A reference to an object is itself a value type. When you use readonly with a reference type, you are making the reference to your object immutable, not enforcing immutability on the memory in which the object lives.

Now, consider an object that contains values types and references to other objects, and those objects contain value types and references to other objects. If you were to compose your objects in such a way that all fields in all objects are readonly you can, in essence, achieve the immutability you desire.

查看更多
看我几分像从前
6楼-- · 2019-01-17 03:11

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!)

查看更多
登录 后发表回答