首页 > 解决方案 > 具有默认值的对象初始值设定项内的集合初始值设定项

问题描述

我刚刚偶然发现了以下问题:

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该类的人),想要坚持最小意外原则,能做些什么来防止我的库的使用者落入这个陷阱吗?


脚注:

标签: c#api-designcollection-initializerleast-astonishment

解决方案


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

在您的代码中起作用的原因AllowedIds = { 1, 2 }是因为 C# 使用鸭子类型来调用Add集合上的方法。如果排除了编译器找到Add方法的可能性,就会在该行出现编译错误AllowedIds = { 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 设置新集合,同时防止您提到的陷阱。


推荐阅读