C# 具有默认值的对象初始值设定项中的集合初始值设定项

C# 具有默认值的对象初始值设定项中的集合初始值设定项,c#,api-design,collection-initializer,least-astonishment,C#,Api Design,Collection Initializer,Least Astonishment,我刚刚偶然发现了以下问题: 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 }

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

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
}
类设置
{
//让我们设置一些默认值:{1}
public ICollection AllowedIds=new List(){1};
}
静态void Main(字符串[]参数)
{
var s=新设置
{
AllowedId={1,2}
};
Console.WriteLine(string.Join(“,”,s.allowedId));//打印1,1,2
}
我理解为什么会发生这种情况:
AllowedIds={1,2}
不是赋值,而是对象初始值设定项中的集合初始值设定项,即它是
AllowedIds.Add(1)的隐式调用;AllowedId.Add(2)

不过,对我来说,这是一个骗局,因为它看起来像一个作业(因为它使用
=

作为一名API/库开发人员(假设我是开发
设置
类的人),我希望遵守,我能做些什么来防止我的库的消费者落入这个陷阱?


脚注:

  • 在这种情况下,我可以使用
    ISet/HashSet
    而不是
    ICollection/List
    (因为重复对于
    allowedId
    )没有意义),这将产生
    1,2
    的预期结果。不过,初始化
    AllowedIds={2}
    会产生与直觉相反的
    1,2
    结果

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


如果您不希望
设置
类的用户添加到
允许ID
,为什么要将其作为
ICollection
(包含
添加
方法并表示要添加到中)公开

AllowedIds={1,2}
在代码中起作用的原因是C#使用duck类型来实现。如果您消除了编译器查找
Add
方法的可能性,那么
AllowedIds={1,2}
行将出现编译错误,从而防止陷阱

您可以执行以下操作:

类设置
{
//让我们设置一些默认值:{1}
public IEnumerable AllowedId{get;set;}=新列表{1};
}
静态void Main(字符串[]参数)
{
var s=新设置
{
AllowedId=新列表{1,2}
};
Console.WriteLine(string.Join(“,”,s.allowedId));//打印1,2
}

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

Wow。这是一个惊喜。我怀疑除了在文档中提到它之外,你对此无能为力,但是,我可能在这里错了。这是令人惊讶的。我以前从未注意到这一点。这个解决方案的问题是设置类不再能够很好地控制AllowedIds属性。例如,设置可能需要实现一个
AddAllowedId
方法。初始值是一个列表,但在
Main
中,可能有人将其设置为int数组(它还实现了
IEnumerable
,但不允许添加。我认为设置将需要一个IEnumerable属性getter,然后是一些允许添加的代码。)IDs@Flydog57是的。您描述的是使
设置
类不可变,或者使用
AddAllowedId
方法进行受控突变。但是,因为OP的代码已经公开了
允许ID
作为一个
ICollection
开始,也就是说,他们应该很清楚类是可变的,所以我的答案是防止调用方落入OP描述的陷阱的最简单方法。