Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I allowed to modify properties which are readonly with object initializers?

I have this simple code:

public static void Main(String[] args)
{
    Data data = new Data { List = { "1", "2", "3", "4" } };
    foreach (var str in data.List)
        Console.WriteLine(str);
    Console.ReadLine();
}

public class Data
{
    private List<String> _List = new List<String>();
    public List<String> List
    {
        get { return _List; }
    }
    public Data() { }
}

So when I'm creating a Data class:

Data data = new Data { List = { "1", "2", "3", "4" } };

The list was filled with strings "1", "2", "3", "4" even if it had no set.

Why is this happening?

like image 793
Asbrand Avatar asked Oct 09 '15 12:10

Asbrand


People also ask

How to fix cannot assign to read only property of object?

The error "Cannot assign to read only property of object" occurs when we try to change a property of an object that has been frozen or when a property has been defined with Object.defineProperties (). To solve the error, create a copy of the object or array, or set the property to writable.

Can I assign a read-only property to a class?

Read-only properties may have initializers and may be assigned to in constructors within the same class declaration, but otherwise assignments to read-only properties are disallowed. ( Source) But what confuses me is the compiled output. See an example on the TS Playground.

Can a read-only dependency property be used as a trigger?

However, a read-only dependency property can be used as a property trigger in a style. For example, IsMouseOver is commonly used to trigger changes to the background, foreground, or other visible property of a control when the mouse is over it.

Is there a way to initialize a class with properties in typescript?

As you can see the initially (readonly) property is overwritten without any warning from TS at compile time. I guess this is intentional, because the language behaves the same all the time and there are less edge cases. EDIT: There is also a "sugarized" way to initialize a class with properties in TypeScript (I guess you know that but anyway):


2 Answers

Your object initializer (with collection initializer for List)

Data data = new Data { List = { "1", "2", "3", "4" } };

gets turned into the following:

var tmp = new Data();
tmp.List.Add("1");
tmp.List.Add("2");
tmp.List.Add("3");
tmp.List.Add("4");
Data data = tmp;

Looking at it this way it should be clear why you are, in fact, adding to string1 and not to string2: tmp.List returns string1. You never assign to the property, you just initialize the collection that is returned. Thus you should look at the getter here, not the setter.

However, Tim is absolutely correct in that a property defined in that way doesn't make any sense. This violates the principle of least surprise and to users of that class it's not at all apparent what happens with the setter there. Just don't do such things.

like image 186
Joey Avatar answered Nov 16 '22 00:11

Joey


That is how collection initializers work internally:

Data data = new Data { List = { "1", "2", "3", "4" } };

It is basically equal to

Data _d = new Data();
_d.List.Add("1");
_d.List.Add("2");
_d.List.Add("3");
_d.List.Add("4");
Data data = _d;

And _d.List uses string1 in getter.

[*] More details in C# specification $7.6.10.3 Collection initializers


Change your code to this:

Data data = new Data { List = new List<string>{ "1", "2", "3", "4" } };

And string1 will be empty and string2 will have four items.

like image 45
ASh Avatar answered Nov 16 '22 02:11

ASh