C# object initialization of read only collection properties
Asked Answered
A

4

18

For the life of me, I cannot figure out what is going on in the example piece of C# code below. The collection (List) property of the test class is set as read only, but yet I can seemingly assign to it in the object initializer.

** EDIT: Fixed the problem with the List 'getter'

using System;
using System.Collections.Generic;
using NUnit.Framework;

namespace WF4.UnitTest
{
    public class MyClass
    {
        private List<string> _strCol = new List<string> {"test1"};

        public List<string> StringCollection 
        {
            get
            {
                return _strCol;
            }
        }
    }

    [TestFixture]
    public class UnitTests
    {
        [Test]
        public void MyTest()
        {
            MyClass c = new MyClass
            {
                // huh?  this property is read only!
                StringCollection = { "test2", "test3" }
            };

            // none of these things compile (as I wouldn't expect them to)
            //c.StringCollection = { "test1", "test2" };
            //c.StringCollection = new Collection<string>();

            // 'test1', 'test2', 'test3' is output
            foreach (string s in c.StringCollection) Console.WriteLine(s);
        }
    }
}
Analiese answered 13/4, 2011 at 8:17 Comment(1)
that unusual get is going to cause confusion, btw...Malformation
M
31

This:

MyClass c = new MyClass
{
    StringCollection = { "test2", "test3" }
};

is translated into this:

MyClass tmp = new MyClass();
tmp.StringCollection.Add("test2");
tmp.StringCollection.Add("test3");
MyClass c = tmp;

It's never trying to call a setter - it's just calling Add on the results of calling the getter. Note that it's also not clearing the original collection either.

This is described in more detail in section 7.6.10.3 of the C# 4 spec.

EDIT: Just as a point of interest, I was slightly surprised that it calls the getter twice. I expected it to call the getter once, and then call Add twice... the spec includes an example which demonstrates that.

Mariomariology answered 13/4, 2011 at 8:21 Comment(4)
Thanks for pointing me to that section of the c# spec. After reading through it I can see where that syntax is defined. Personally, I don't see why they would want to allow that syntax (it looks like you are assigning a new set of elements to a property)... or maybe I just think that because I've been writing too much JavaScript lately :)Analiese
@markdb314: It's there because it's incredibly useful. It's not at all unusual to allow a caller to manipulate a collection but not replace it.Mariomariology
You explained why the unusual code does work, but I still don't understand why the first line of commented-out code doesn't work. Given your explanation, I would expect c.StringCollection = { "test1", "test2" }; to be allowed and work the same. But the OP says it doesn't compile.Ibson
@JesseWebb: Collection initializers are only allowed in object creation expressions - either directly on the newly created collection, e.g. new List<string> { "x", "y", "z" } or embedded within an object initializer. c.StringCollection = { "test1", "test2" } is just an assignment - there's no object creation expression there.Mariomariology
M
13

You aren't calling the setter; you are essentially calling c.StringCollection.Add(...) each time (for "test2" and "test3") - it is a collection initializer. For it to be the property assignment, it would be:

// this WON'T work, as we can't assign to the property (no setter)
MyClass c = new MyClass
{
    StringCollection = new StringCollection { "test2", "test3" }
};
Malformation answered 13/4, 2011 at 8:19 Comment(10)
@remi -look at the get; every time you call get it returns a collection with just "test1"Malformation
@Analiese I already answered that... every time you call that get, your code creates a new list. You should really have the list as a field on the class.Malformation
@markdb314: If you actually back your property with a field (which could be initialized via another collection initializer) then you'll end up with a more consistent property... instead of creating a new list on each getter call.Mariomariology
@Analiese Because of this line return new List<string> { "test1" }; in get it always returns new list with one element "test1"Diuresis
@Marc , oh yes ! I didn't see it, my bad. Thank for your answer.Downstairs
For completeness, private readonly List<string> items = new List<string> { "test1" }; public List<string> StringCollection { get { return items;}} should fix that.Malformation
ok, duh. of course that's why it is only returning "test1", my bad. but i still don't understand where that syntax comes from? for example, why doesn't c.StringCollection = { "test1", "test2" } do the same thing when placed outside an object initializer?Analiese
public class MyClass { private List<String> list = new List<string>() { "test1" }; public List<string> StringCollection { get { return list; } } }Knar
@markdb314: Because collection initializers are only valid as part of an object-creation-expression (section 7.6.10 of the C# spec).Mariomariology
thanks @Jon - I didn't have the spec numbers to hand; obliged ;pMalformation
M
0

I think that, beeing read only, you can't do

c.StringCollection = new List<string>();

But you can assign items to list...
Am I wrong?

Monger answered 13/4, 2011 at 8:21 Comment(0)
G
-1

The StringCollection property doesn't have a setter so unless you add one you cannot modify its value.

Greet answered 13/4, 2011 at 8:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.