c# - 具有默认值的对象初始值设定项内的集合初始值设定项
问题描述
我刚刚偶然发现了以下问题:
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
该类的人),想要坚持最小意外原则,我能做些什么来防止我的库的使用者落入这个陷阱吗?
脚注:
在那种特殊情况下,我可以使用 an
ISet/HashSet<int>
而不是ICollection/List
(因为重复对 没有意义AllowedIds
),这将产生1, 2
. 尽管如此,初始化AllowedIds = { 2 }
会产生反直觉的结果1, 2
。我在C# github repo上找到了一个相关的讨论,基本上得出的结论是,是的,这种语法令人困惑,但它是一个旧功能(2006 年引入),我们无法在不破坏向后兼容性的情况下更改它。
解决方案
如果您不希望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 设置新集合,同时防止您提到的陷阱。
推荐阅读
- python - python包的版本管理
- reactjs - 学习 React:无法理解为什么我的组件不会渲染/API/KEYS
- python - Python Scrapy 提取子
- reactjs - React 呈现完整的组件,而不是使用包装函数时发生的变化
- shell - 使用 unix "time" 命令将时间输出到文件,但将命令的输出留给控制台
- javascript - 使用 ajax 和 pug (jade) 更新 html 表
- python - 有没有办法在 Python 中为具有特定模式的二维数组分配自动值,而不用只用一个值填充整个数组?
- c - C - 在 connect() 处冻结
- r - 在 purrr 工作流程中使用 mutate
- react-native - React Navigation 隐藏/显示导航栏