1

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?

Stijn Van Antwerpen
  • 1,840
  • 17
  • 42

2 Answers2

4

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.

Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 3
    Well, that "overloaded call" error does give us an interesting way to do this: `Bars = { items.Select(s => new Bar { Prop = s }) }` will be accepted if we write an `public static void Add(this ICollection collection, IEnumerable items)` extension method. I wouldn't be a fan of this (how easy is it to get confused by leaving out the braces -- and what happens if we have a collection of collections?), but it's something. – Jeroen Mostert Nov 22 '18 at 13:18
  • @JeroenMostert I would love to turn that so cool comment into an answer, but how...? Can we make up a question together? Maybe talk somewhere in chat? – Patrick Hofman Nov 22 '18 at 14:03
  • @PatrickHofman: as I see it, that remark could just be folded into one of the answers on this question. I wouldn't make it an answer myself since, as I mentioned, I don't really want to see this in production code (although, thinking about it, I may actually have used a variation on this myself somewhere -- bad me). I don't care a blot about attribution, so feel free to steal it if you're so inclined. – Jeroen Mostert Nov 22 '18 at 14:06
  • 1
    @CodeCaster Thanks for adding that. – Patrick Hofman Nov 22 '18 at 14:11
2

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.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Well no, The second one would work if Bars would not be read-only. It fails because it is read-only. I want is to Add every item in the selector. – Stijn Van Antwerpen Nov 22 '18 at 13:11
  • 1
    That is what I said in other words. Since it is an assignment, the property can't be read-only. So don't assign but call `Add` instead. – Patrick Hofman Nov 22 '18 at 13:12
  • 1
    @MichaWiedenmann: that's a regular object initializer, not a collection initializer. The syntax `Bar = { Prop = ... }` is different from `Bar = { Prop }`, despite the superficial resemblance. Whether or not `Bar` is readonly doesn't matter for the object initializer, as long as `Prop` is writable -- it doesn't assign a new `Bar`. This is documented in the [draft specification](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#object-initializers), so it should go all the way back to C# 6. – Jeroen Mostert Nov 22 '18 at 13:57
  • 1
    @MichaWiedenmann: `{ Bar = new Bar { Prop = ... } }` is an object initializer that initializes the property `Bar` with a newly allocated `Bar` object of which the `Prop` field or property is initialized. This will fail if `Bar` is read-only (in all language versions). `{ Bar = { Prop = ... } }` is a nested object initializer that will initialize the `Prop` field or property of `Bar`, which will work even if `Bar` is read-only. They're both object initializers (albeit with different semantics), not collection initializers. – Jeroen Mostert Nov 22 '18 at 14:48
  • @JeroenMostert TIL nested object initializer. Thank you for your patience. – Micha Wiedenmann Nov 22 '18 at 15:01