首页 > 解决方案 > c# 中只读属性的对象初始化器

问题描述

如果你有课:

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

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

您可以使用对象初始化,例如:

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

如果你有课

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

你可以写

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

但是,我想写一些类似的东西

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

但是,上面的代码不能编译:

无法将 IEnumerable 分配给 Bar

我不能写:

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

属性栏是只读的。

这个可以存档吗?

标签: c#projectionreadonlyobject-initializers

解决方案


如果您阅读实际的编译器错误(以及集合初始化程序的文档),您会发现集合初始化程序只是Add()调用的语法糖:

CS1950:最好的重载集合初始化方法System.Collections.Generic.ICollection<Bar>.Add(Bar)有一些无效参数

CS1503:参数#1无法将System.Collections.Generic.IEnumerable<Bar>表达式转换为类型Bar

所以语法SomeCollection = { someItem }将被编译为SomeCollection.Add(someItem). 而且您不能添加IEnumerable<Bar>Bars 的集合中。

您需要手动添加所有项目:

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

或者,给定更短的代码是您的目标,在Foo2的构造函数中执行相同的操作:

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

然后你可以像这样初始化 Foo2:

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

对于@JeroenMostert 假设的对集合初始化器语法的有趣滥用,您可以使用扩展方法:

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

这允许这样做:

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

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

    

但这太恶心了。


推荐阅读