ReadonlyCollection, are the objects immutable?

2020-04-10 01:18发布

问题:

I'm trying using ReadOnlyCollection to make object immutable, I want the property of object are immutable.

public ReadOnlyCollection<FooObject> MyReadOnlyList
{
    get
    {
        return new ReadOnlyCollection<FooObject>(_myDataList);
    }
}

But I little confused.

I tried to change the property of the object in to MyReadOnlyList using a foreach and ... I can change value property, is it correct? I understood ReadOnlyCollection set an add level to make the object immutable.

回答1:

The fact that ReadOnlyCollection is immutable means that the collection cannot be modified, i.e. no objects can be added or removed from the collection. This does not mean that the objects it contains immutable.

This article by Eric Lippert, explains how different kinds of immutability work. Basically, a ReadOnlyCollection is an immutable facade which can read the underlying collection (_myDataList), but cannot modify it. However, you can still change the underlying collection since you have a reference to _myDataList by doing something like _myDataList[0] = null.

Furthermore, the objects returned by ReadOnlyCollection are the same ones returned by _myDataList, i.e. this._myDataList.First() == this.MyReadOnlyList.First() (with LINQ). This means that if an object in _myDataList is mutable, then so is the object in MyReadOnlyList.

If you want the objects to be immutable, you should design them accordingly. For instance, you might use:

public struct Point
{
    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }

    // In C#6, the "private set;" can be removed
    public int X { get; private set; }
    public int Y { get; private set; }
}

instead of:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Edit: in this case, as noted by Ian Goldby, neither struct allows you to modify properties of the elements in the collection. This happens because structs are value types and when you access an element the collection returns a copy of the value. You can only modify the properties of a Point type if it is a class, which would mean that references to the actual objects are returned, instead of copies of their values.



回答2:

I tried to change the property of the object in to MyReadOnlyList using a foreach and ... I can change value property, is it correct? I understood ReadOnlyCollection set an add level to make the object immutable.

Using a ReadOnlyCollection does not make any guarantees as for the object that is stored in the collection. All it guarantees is that the collection cannot be modified once it has been created. If an element is retrieved from it, and it has mutable properties, it can very well be modified.

If you want to make your FooObject an immutable one, then simply do so:

public class FooObject
{
    public FooObject(string someString, int someInt)
    {
        SomeString = someString;
        SomeInt = someInt;
    }

    public string SomeString { get; };
    public int SomeInt { get; };
}


回答3:

What is immutable is the collection itself, not the objects. For now, C# doesn't support immutable objects without wrapping them as ReadOnlyCollection<T> does in your case.

Well, you can still create immutable objects if their properties have no accessible setter. BTW, they're not immutable at all because they can mutate from a class member that may have equal or more accessibility than the setter.

// Case 1
public class A
{
    public string Name { get; private set; }

    public void DoStuff() 
    {
        Name = "Whatever";
    }
}

// Case 2
public class A
{
    // This property will be settable unless the code accessing it
    // lives outside the assembly where A is contained...
    public string Name { get; internal set; }
}

// Case 3
public class A
{
    // This property will be settable in derived classes...
    public string Name { get; protected set; }
}

// Case 4: readonly fields is the nearest way to design an immutable object
public class A
{
     public readonly string Text = "Hello world";
}

As I said before, reference types are always mutable by definition and they can behave as immutable under certain conditions playing with member accessibility.

Finally, structs are immutable but they're value types and they shouldn't be used just because they can represent immutable data. See this Q&A to learn more about why structs are immutable: Why are C# structs immutable?