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描述的陷阱的最简单方法。