【问题标题】:Collection initializers inside object initializers with default values具有默认值的对象初始值设定项内的集合初始值设定项
【发布时间】:2020-09-21 01:45:56
【问题描述】:

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

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 类的人)想要遵守 principle of least surprise我可以做些什么来阻止我的库的消费者不会掉进那个陷阱?


脚注:

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

  • 我在C# github repo上找到了一个相关的讨论,基本上得出的结论是,是的,这个语法很混乱,但它是一个旧特性(2006年引入),我们不能改变它而不向后打破兼容性。

【问题讨论】:

  • 哇。这是一个惊喜。我怀疑除了在文档中提到它之外,您无能为力,但话又说回来,我在这里可能错了。
  • 这令人惊讶。我以前从未注意到这一点。

标签: c# api-design collection-initializer least-astonishment


【解决方案1】:

如果您不希望 Settings 类的用户添加AllowedIds,为什么将其公开为 ICollection&lt;int&gt;(其中包含 Add 方法并表示意图加入)?

AllowedIds = { 1, 2 } 在您的代码中起作用的原因是因为 C# 使用鸭子类型来输入 call the Add method on the collection。如果排除编译器找到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设置一个新的集合,同时防止你提到的陷阱。

【讨论】:

  • 这个解决方案的问题在于,Settings 类不再真正很好地控制 AllowedIds 属性。例如,设置可能想要实现AddAllowedId 方法。初始值是一个列表,但在Main 中,有人可以将它设置为一个int 数组(它也实现IEnumerable &lt;int&gt;,但不允许添加。我认为设置需要有一个IEnumerable 属性getter然后是一些允许添加 ID 的代码
  • @Flydog57 是的。您所描述的是使 Settings 类不可变或具有用于受控突变的 AddAllowedId 方法。但是,由于 OP 的代码一开始就已经将 AllowedIds 暴露为 ICollection&lt;int&gt;,也就是说,他们应该很清楚类是可变的,所以我的回答是防止调用者陷入 OP 描述的陷阱的最简单方法。
猜你喜欢
  • 2016-01-22
  • 2012-12-24
  • 1970-01-01
  • 1970-01-01
  • 2013-01-23
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
相关资源
最近更新 更多