具有默认值的对象初始值设定项内的集合初始值设定项

Collection initializers inside object initializers with default values

提问人:Heinzi 提问时间:6/2/2020 最后编辑:Heinzi 更新时间:6/10/2020 访问量:302

问:

我只是偶然发现了以下问题:

class Settings
{
    // Let's set some default value: { 1 }
    public ICollection<int> AllowedIds = new List<int>() { 1 };
}

static void Main(string[] args)
{
    var s = new Settings
    {
        AllowedIds = { 1, 2 }
    };

    Console.WriteLine(string.Join(", ", s.AllowedIds)); // prints 1, 1, 2
}

我理解为什么会发生这种情况:不是赋值,而是对象初始值设定项中的集合初始值设定项,即它是 的隐式调用。AllowedIds = { 1, 2 }AllowedIds.Add(1); AllowedIds.Add(2)

不过,对我来说,这是一个陷阱,因为它看起来像一个作业(因为它使用 ).=

作为一个 API/库开发人员(假设我是开发类的人),想要坚持最小意外原则我能做些什么来防止我的库的使用者落入这个陷阱吗?Settings


脚注:

  • 在这种特殊情况下,我可以使用代替(因为重复项对 没有意义),这将产生 的预期结果。尽管如此,初始化仍会产生违反直觉的结果。ISet/HashSet<int>ICollection/ListAllowedIds1, 2AllowedIds = { 2 }1, 2

  • 我在 C# github 存储库上找到了一个相关的讨论,它基本上得出的结论是,是的,这个语法令人困惑,但它是一个旧功能(于 2006 年引入),如果不破坏向后兼容性,我们就无法更改它。

C# API 设计 集合初始值设定项 最小惊奇

评论

0赞 Zohar Peled 6/2/2020
哇。这是一个惊喜。我怀疑除了在文档中提到它之外,你对此无能为力,但话又说回来,我在这里可能错了。
1赞 Flydog57 6/2/2020
这是令人惊讶的。我以前从未注意到这一点。

答:

0赞 rexcfnghk 6/2/2020 #1

如果您不希望类的用户添加到,为什么要将其公开为(其中包含一个方法并表示要添加到的意图)?SettingsAllowedIdsICollection<int>Add

之所以在代码中起作用,是因为 C# 使用 duck 类型来调用集合上的 Add 方法。如果排除编译器找到方法的可能性,则行上将出现编译错误,从而防止陷阱。AllowedIds = { 1, 2 }AddAllowedIds = { 1, 2 }

您可以执行以下操作:

class Settings
{
    // Let's set some default value: { 1 }
    public IEnumerable<int> AllowedIds { get; set; } = new List<int> { 1 };
}

static void Main(string[] args)
{
    var s = new Settings
    {
        AllowedIds = new List<int> { 1, 2 }
    };

    Console.WriteLine(string.Join(", ", s.AllowedIds)); // prints 1, 2
}

这样,您仍然允许调用方使用 setter 设置新的集合,同时防止您提到的陷阱。

评论

0赞 Flydog57 6/3/2020
此解决方案的问题在于 Settings 类不再真正很好地控制 AllowedIds 属性。例如,Settings 可能想要实现一个方法。初始值是一个列表,但在 中,有人可以将其设置为 int 数组(该数组也实现了 ,但不允许添加。我认为 Settings 需要有一个 IEnumerable 属性 getter,然后是一些代码来允许添加 IDAddAllowedIdMainIEnumerable <int>
0赞 rexcfnghk 6/3/2020
@Flydog57是的。你所描述的是使类不可变或有一个受控突变的方法。但是,由于 OP 的代码一开始就已经公开了,即他们应该很清楚类是可变的,所以我的回答是防止调用者落入 OP 所描述的陷阱的最简单方法。SettingsAddAllowedIdAllowedIdsICollection<int>