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

2

7

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?

Latoshalatouche answered 9/10, 2015 at 12:0 Comment(9)
You are adding elements to List string2 and then you are reading List string1 makes no sense to me.Aby
indeed. But List string2 is epmty after adding elementsLatoshalatouche
How do you know that?? You aren't checking List string2 anywhere in your code.Aby
@kevintjuh93: Just look at the code, it's pretty clear that string2 must be empty.Wiltonwiltsey
Oh yeah, forgot that it gets convert to Add which uses the getAby
Take look at this link #5646785Yamauchi
I have modified your question significantly. Feel free to undo that if it was too radical.Brucie
@TimSchmelter readonly is a keyword (in your new title) that doesn't appear anywhere in the code and doesn't appear to be related to the question in any way. Was this intended? (It also makes all references to string1 and string2 in the comments and answers nonsensical.)Basidium
@GalacticCowboy: i thought readonly property would be more concise than properties without setter. Titles are there to help future people with similar problems to find this question. Also, even msdn mentions that "a property without a set accessor is considered read-only". You're right, i have overlooked that even the accepted answer referred to these (bad named) fields. I just wanted to make the question as helpful as possible after the answer was accepted. Feel free to edit the question or the answer.Brucie
W
12

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.

Wiltonwiltsey answered 9/10, 2015 at 12:8 Comment(7)
Sure, it's not code for real task, I just wanna know how things works = )Latoshalatouche
@Asbrand: If you're interested in how things work I recommend you download ILSpy and poke in the compiler output. Or read the C# specification. Both take a bit of getting used to, of course, but as learning resources they're invaluable.Wiltonwiltsey
@Joey: instead of a decompiler i would use referencesource.microsoft.com Also note, that i've deleted my answer meanwhile.Brucie
@Tim: Both, actually. The decompiler tells you pretty precisely what the compiler does with the code you wrote. Reference source is for clarification what's going on in the framework. Of course, historically that second point was also answered by a decompiler.Wiltonwiltsey
@Latoshalatouche It is what it really does, I decompiled your sample and it does exactly what @Wiltonwiltsey says. The only difference is that it doesn't use tmp, but all is done immediately on dataStenophyllous
@Joey: sometimes the decompiler produces strange results(stackalloc byte[1 * 114 / 1]) whereas the source is different. Formatting, comments are lost and even code is changed(f.e. LINQ query syntax -> method syntax). So i look at the original source if it's .NET code.Brucie
@Tim: My point was that the decompiler helps you to understand how C# source code gets converted into lower-level operations by the compiler. In this case how collection initializers are expanded. This is not at all about reading something akin to the original source of the framework. I meant actual IL, not decompiled C# source.Wiltonwiltsey
H
4

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.

Hyacinthhyacintha answered 9/10, 2015 at 12:8 Comment(2)
change your code part will throw exception Property or indexer 'Program.Data.List' cannot be assigned to -- it is read only and this is actually the reason of confusing why property without setter looks like settable in first place.Vowelize
@Sinatr, Tim Schmelter significantly edited question! check edit history. I should agree, 2nd example doesn't work after the editHyacinthhyacintha

© 2022 - 2024 — McMap. All rights reserved.