Object initializer for readonly properties in c#

1.9k Views Asked by At

If you have the class:

class Foo {
      Bar Bar { get; } = new Bar();
}

class Bar {
      string Prop {get; set; }
}

You can use a object initialise like:

var foo = new Foo { 
    Bar = { Prop = "Hello World!" }
}

If you have a class

class Foo2 {
      ICollection<Bar> Bars { get; } = new List<Bar>();
}

You can write

var foo = new Foo2 { 
    Bars = { 
        new Bar { Prop = "Hello" }, 
        new Bar { Prop = "World" }
    }
}

but, I would like to write something like

var items = new [] {"Hello", "World"};
var foo = new Foo2 { 
    Bars = { items.Select(s => new Bar { Prop = s }) }
}

However, the code above does not compile with:

cannot assigne IEnumerable to Bar

I cannot write:

var foo = new Foo2 { 
    Bars = items.Select(s => new Bar { Prop = s })
}

Property Bars is readonly.

Can this be archived?

2

There are 2 best solutions below

4
On BEST ANSWER

If you read the actual compiler errors (and the docs for collection initializers), you'll find that collection initializers are merly syntactic sugar for Add() calls:

CS1950: The best overloaded collection initalizer method System.Collections.Generic.ICollection<Bar>.Add(Bar) has some invalid arguments

CS1503: Argument #1 cannot convert System.Collections.Generic.IEnumerable<Bar> expression to type Bar

So the syntax SomeCollection = { someItem } will be compiled to SomeCollection.Add(someItem). And you can't add IEnumerable<Bar> to a collection of Bars.

You need to manually add all items:

foreach (bar in items.Select(s => new Bar { Prop = s }))
{
    foo.Bars.Add(bar);
}

Or, given shorter code is your goal, do the same in Foo2's constructor:

public class Foo2 
{
    public ICollection<Bar> Bars { get; }
    
    public Foo2() : this(Enumerable.Empty<Bar>()) { }
    
    public Foo2(IEnumerable<Bar> bars)
    {
        Bars = new List<Bar>(bars);
    }
}

Then you can initialize Foo2 like this:

var foo = new Foo2(items.Select(...));

For a funny abuse of the collection initializer syntax as supposed by @JeroenMostert, you could use an extension method:

public static class ICollectionExtensions
{
    public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            collection.Add(item);
        }
    }
}

Which allows this:

public class Foo
{
    public ICollection<string> Bar { get; } = new List<string>();
}

var foo = new Foo
{
    Bar = { new [] { "foo", "bar", "baz" } }
};

    

But that's just nasty.

5
On

Bars = { ... } Doesn't do an assignment. Instead it calls Add for every item in the initializer. That is why it doesn't work.

That is why Bars = items.Select(s => new Bar { Prop = s }) gives the same error: it is an assignment, not a list to add.

There is no option other that using a constructor to pass in the values, or use regular Add or AddRange statements after the constructor has ran.