Инициализаторы коллекций внутри инициализаторов объектов со значениями по умолчанию
Я только что наткнулся на следующую проблему:
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
class), который хочет придерживаться принципа наименьшего удивления, могу ли я что-нибудь сделать, чтобы потребители моей библиотеки не попали в эту ловушку?
Сноски:
В этом конкретном случае я мог бы использовать
ISet/HashSet<int>
вместо тогоICollection/List
(поскольку дубликаты не имеют смысла дляAllowedIds
), что дало бы ожидаемый результат1, 2
. Тем не менее, инициализацияAllowedIds = { 2 }
даст противоречащий интуиции результат1, 2
.Я нашел соответствующее обсуждение репозитория C# на github, в котором был сделан вывод, что да, этот синтаксис сбивает с толку, но это старая функция (представленная в 2006 году), и мы не можем изменить ее, не нарушив обратную совместимость.
1 ответ
Если вы не ожидаете, что пользователь 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
}
Таким образом, вы по-прежнему позволяете вызывающей стороне устанавливать новую коллекцию с помощью установщика, предотвращая указанную вами ловушку.