Инициализаторы коллекций внутри инициализаторов объектов со значениями по умолчанию

Я только что наткнулся на следующую проблему:

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/ библиотеки (допустим, я разрабатываю Settingsclass), который хочет придерживаться принципа наименьшего удивления, могу ли я что-нибудь сделать, чтобы потребители моей библиотеки не попали в эту ловушку?


Сноски:

  • В этом конкретном случае я мог бы использовать 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
}

Таким образом, вы по-прежнему позволяете вызывающей стороне устанавливать новую коллекцию с помощью установщика, предотвращая указанную вами ловушку.

Другие вопросы по тегам