我只是偶然发现了以下问题。
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)
.
不过,对我来说,这也是一个陷阱,因为它 样子 像赋值一样(因为它使用了 =
).
作为一个APIlibrary开发者(比方说,我是开发APIlibrary的人。Settings
类)的人,谁愿意坚持 最低限度原则, 有什么办法可以防止我库的消费者掉进这个陷阱吗?
脚注。
在这种特殊情况下,我可以使用 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设置一个新的集合, 同时防止你提到的陷阱.