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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Asbrand
  • 99
  • 5
  • 4
    You are adding elements to `List string2` and then you are reading `List string1` makes no sense to me. – kevintjuh93 Oct 09 '15 at 12:02
  • indeed. But List string2 is epmty after adding elements – Asbrand Oct 09 '15 at 12:04
  • 2
    How do you know that?? You aren't checking `List string2` anywhere in your code. – kevintjuh93 Oct 09 '15 at 12:04
  • 2
    @kevintjuh93: Just look at the code, it's pretty clear that `string2` must be empty. – Joey Oct 09 '15 at 12:06
  • Oh yeah, forgot that it gets convert to `Add` which uses the `get` – kevintjuh93 Oct 09 '15 at 12:10
  • Take look at this link http://stackoverflow.com/questions/5646285/c-sharp-object-initialization-of-read-only-collection-properties – Arash Oct 09 '15 at 12:16
  • I have modified your question significantly. Feel free to undo that if it was too radical. – Tim Schmelter Oct 09 '15 at 12:27
  • @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.) – GalacticCowboy Oct 13 '15 at 20:16
  • @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. – Tim Schmelter Oct 13 '15 at 23:42

2 Answers2

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.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • 1
    Sure, it's not code for real task, I just wanna know how things works = ) – Asbrand Oct 09 '15 at 12:16
  • 2
    @Asbrand: If you're interested in how things work I recommend you download [ILSpy](http://ilspy.net/) 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. – Joey Oct 09 '15 at 12:20
  • @Joey: instead of a decompiler i would use http://referencesource.microsoft.com/ Also note, that i've deleted my answer meanwhile. – Tim Schmelter Oct 09 '15 at 12:30
  • @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. – Joey Oct 09 '15 at 12:31
  • @Asbrand It is what it really does, I decompiled your sample and it does exactly what @Joey says. The only difference is that it doesn't use `tmp`, but all is done immediately on `data` – Matteo Umili Oct 09 '15 at 12:31
  • @Joey: sometimes the decompiler produces [strange results](http://stackoverflow.com/a/15294922/284240)(`stackalloc byte[1 * 114 / 1]`) whereas the [source is different](http://referencesource.microsoft.com/#mscorlib/system/number.cs,fc92e899c667fab0). 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. – Tim Schmelter Oct 09 '15 at 12:36
  • @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. – Joey Oct 09 '15 at 14:43
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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ASh
  • 34,632
  • 9
  • 60
  • 82
  • 1
    *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. – Sinatr Oct 09 '15 at 13:37
  • 1
    @Sinatr, Tim Schmelter significantly edited question! check edit history. I should agree, 2nd example doesn't work after the edit – ASh Oct 09 '15 at 13:40