Object initializer for readonly properties in c#

跟風遠走 提交于 2020-07-10 07:10:24

问题


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?


回答1:


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.




回答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.



来源:https://stackoverflow.com/questions/53431659/object-initializer-for-readonly-properties-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!