2

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

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


脚注:

  • 在那种特殊情况下,我可以使用 anISet/HashSet<int>而不是ICollection/List(因为重复对 没有意义AllowedIds),这将产生1, 2. 尽管如此,初始化AllowedIds = { 2 }会产生反直觉的结果1, 2

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

4

1 回答 1

0

如果您不希望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 设置新集合,同时防止您提到的陷阱。

于 2020-06-02T14:55:32.780 回答