Можно ли смешивать инициализатор объекта и инициализатор коллекции? - PullRequest
14 голосов
/ 04 октября 2011

Я определяю инициализатор коллекции с IEnumerable, как указано здесь: http://msdn.microsoft.com/en-us/library/bb384062.aspx

Теперь я могу создавать объекты в инициализаторе моей коллекции, и они добавляются с помощью моего метода Add () следующим образом:

class ArrangedPanel : RectElement
{
    private List<RectElement> arrangedChildren = new List<RectElement>();
    public int Padding = 2;

    public void Add(RectElement element)
    {
        arrangedChildren.Add(element);
        //do custom stuff here
    }

    public IEnumerator GetEnumerator()
    {
        return arrangedChildren.GetEnumerator();
    }
}

// Somewhere
debugPanel.Add(new ArrangedPanel() 
{ 
    new ButtonToggle(),
    new ButtonToggle()
});

Однако, если я пытаюсь установить свойство, такое как поле "Padding", я получаю сообщение об ошибке в инициализаторах коллекции.

debugPanel.Add(new ArrangedPanel() 
{ 
    Padding = 5,
    new ButtonToggle(),
    new ButtonToggle()
});

Можно ли установить как инициализаторы коллекций, так и инициализаторы объектов?

Ответы [ 5 ]

12 голосов
/ 04 октября 2011

К сожалению, невозможно смешать инициализаторы объекта и коллекции.Спецификация C # 3.0 определяет выражение создания объекта в разделе 7.5.10.1 как:

    object-creation-expression:
      <b>new</b>   <i>type</i>   (   argument-list<sub>opt</sub>   )   object-or-collection-initializer<sub>opt</sub>
      <b>new</b>   <i>type</i>   object-or-collection-initializer

Как и следовало ожидать, object-or-collection-initializer является либо инициализатором объекта , либо инициализатором коллекции.Синтаксис для объединения затем отсутствует.

10 голосов
/ 07 марта 2013

У меня была похожая проблема.Очевидно, что самое близкое, что можно получить, это добавить свойство к классу, которое разрешает доступ инициализатора коллекции:

В ArrangedPanel:

public ArrangedPanel Container {
   get { return this; }
}

И в коде:

debugPanel.Add(new ArrangedPanel() 
{ 
    Padding = 5,
    Container = {
        new ButtonToggle(),
        new ButtonToggle()
    }
});

неплохо, наверное?

@ Редактировать: в соответствии с комментарием @Tseng я изменил возвращаемое значение нового свойства, чтобы оно возвращало сам ArrangedObject вместо его List<RectElement> члена.Таким образом вызывается метод ArrangedPanel.Add и любая (потенциально более сложная) логика в нем используется повторно.

@ Edit2: переименовали свойство ('Children' -> 'Container') в надежде, что новое имя лучше отражает новое значение.

2 голосов
/ 22 марта 2017

Еще одна возможность, без зависимости от порядка и неоднозначности типов, хотя и очень явная и длинная.

public class PaddingSetter
{
    public Padding Value { get; private set; }

    public PaddingSetter()
    {
        Value = new Padding(5);
    }
}

...

public void Add(PaddingSetter setter)
{
    Padding = setter.Value;
}

...

new ArrangedPanel() 
{ 
    new PaddingSetter(5),
    new ButtonToggle(),
    new ButtonToggle()
}
1 голос
/ 09 марта 2013

Теоретически инициализаторы места хранения должны создавать новые объекты с заданным состоянием, а не заставлять объекты проходить через ряд состояний. Код, который должен что-то вводить через ряд состояний, должен быть написан в конструкторе объекта.

Инициализация места хранения (поля, переменной и т. Д.) Константой просто устанавливает его значение один раз. Инициализация переменной с помощью конструктора или вызова метода приведет к тому, что методу будет дано все, что ему нужно, до его запуска, но он ничего не изменит с переменной, пока метод не вернется. Таким образом, это также позволяет избежать любой явной последовательности состояний.

Инициализаторы свойств и инициализаторы коллекций нарушают этот шаблон, но работают в предположении, что многие классы спроектированы таким образом, что создание объекта и установка некоторых свойств перед раскрытием ссылки на внешний код приведет к результатам, неотличимым от объекта, мгновенно возникающего в результате существования. с этими свойствами. Аналогично, инициализаторы коллекции предполагают, что создание коллекции и последующий вызов Add с последовательностью элементов приведут к результатам, неотличимым от появления коллекции, содержащей правильные значения.

Не все классы, которые предоставляют свойства, приведут к ожидаемому поведению при использовании с инициализаторами свойств, и не все классы выглядят как коллекции, которые будут давать ожидаемое поведение при использовании с инициализаторами коллекций, но компилятор готов «угадать» те классы, которые использование с инициализаторами свойств будет соответствовать обычному шаблону свойств, а также для классов, которые используются с инициализаторами коллекций.

Если для настройки объекта потребуются как вызывающие установщики свойств, так и метод item-Add, это будет означать, что объект не является типичными объектами с установщиками свойств и не является типичной коллекцией. Не будет особой причины для того, чтобы язык диктовал, что установщики свойств будут вызываться до добавления элементов, и не будет особой причины указывать, что они будут вызываться после. Можно было бы позволить исходному коду C # для инициализаторов указать порядок, в котором они выполняются, но такая спецификация явно признала бы, что последовательность операций будет влиять на состояние объекта способами, не выраженными в самом инициализаторе. Такие побочные эффекты подразумевают, что рассматриваемый код принадлежит конструктору, а не инициализатору поля.

0 голосов
/ 10 сентября 2013

Я не рекомендую в этом случае, но возможно использование нескольких Add перегрузок.

Так что в ArrangedPannel включается

public void Add(int padding)
{
    Padding = padding;
}

Тогда может быть определено в коде как

debugPanel.Add(new ArrangedPanel() 
{ 
    5, // is this Padding?
    new ButtonToggle(),
    new ButtonToggle()
});

Но я предпочитаю ответ @ Haymo, потому что здесь неясно, какое значение '5' установлено, и множественные свойства int, вероятно, приведут к сумасшедшему коду, подобному

public void Add(int intProp)
{
    var current = intPropSetCount++;
    switch(current)
    {
        case 0: Padding = intProp; return;
        case 1: SecondProp = intProp; return;
        // ...
        default: throw new Exception();
    }
}

Эта идея, вероятно, лучше всего оставить для объединения нескольких коллекций в одну оболочку.

...