Как работает этот список?

Я видел этот пример кода и похоже, что он назначает инициализатор массива в List. Я думал, что это не сработает, но как-то это скомпилируется. Является {} не инициализатором массива? Дети имеют тип IList. Как это работает без "нового списка" перед фигурными фигурными скобками?

        var nameLayout = new StackLayout()
        {
            HorizontalOptions = LayoutOptions.StartAndExpand,
            Orientation = StackOrientation.Vertical,
            Children = { nameLabel, twitterLabel }
        };

Изменить: когда я попробовал Children = new List<View>{ nameLabel, twitterLabel }, компилятор выдает это предупреждение: "Свойство или указатель Layout.Children нельзя назначить, он доступен только для чтения".

Фрагмент кода от Xamarin кстати: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/

Ответ 1

Это частный случай инициализатора коллекции.

В С#, фигурные скобки инициализатора массива были обобщены для работы с любым конструктором класса коллекции.

Любой класс поддерживает те, что реализует System.Collections.IEnumerable и имеет один или несколько методов Add(). Эрик Липперт имеет хорошую статью об этом типе "сопоставления шаблонов" в С#: то, что делает компилятор здесь, это то, что они называют " duck typing", а не обычный строго типизированный OOP, где возможности класса распознаются на основе наследования и реализации интерфейса. С# делает это в нескольких местах. В этой статье я не знал много материала.

public class Foo : List<String>
{
    public void Add(int n)
    {
        base.Add(n.ToString());
    }
    public void Add(DateTime dt, double x)
    {
        base.Add($"{dt.ToShortDateString()} {x}");
    }
}

И затем это компилируется:

var f = new Foo { 0, 1, 2, "Zanzibar", { DateTime.Now, 3.7 } };

Этот синтаксический сахар для этого:

var f = new Foo();

f.Add(0);
f.Add(1);
f.Add(2)
f.Add("Zanzibar");
f.Add(DateTime.Now, 3.7);

С этими играми можно играть в довольно странные игры. Я не знаю, неплохо ли все исправить (на самом деле я знаю - это не так), но вы можете. Я написал класс парсера командной строки, где вы можете определить параметры через инициализатор коллекции. Он получил более дюжины перегрузок Add с переменными списками параметров, многие из которых носят общий характер. Все, что может сделать компилятор, - честная игра.

Опять же, вы можете подтолкнуть это к уменьшению отдачи к проблеме злоупотребления функциями.

То, что вы видите, - это расширение того же синтаксиса инициализатора, где оно позволяет вам инициализировать коллекцию для не назначаемого члена, который уже создал сам класс:

public class Bar
{
    public Foo Foo { get; } = new Foo();
}

И теперь...

var b = new Bar { Foo = { 0, "Beringia" } };

{ 0, "Beringia" } - инициализатор коллекции для экземпляра Foo, который Bar создан для себя; это синтаксический сахар для этого:

var b = new Bar();

b.Foo.Add(0);
b.Foo.Add("Beringia");

Желание компилятора разрешить перегрузки Foo.Add() в использовании инициализатора синтаксиса-сахара имеет смысл, когда вы смотрите на него таким образом. Я считаю, что это здорово, но я не на 100% удобен с синтаксисом, который они выбрали. Если вы обнаружили, что оператор присваивания является красной селедкой, другие тоже.

Но я не Арбитр Синтаксиса, и это, вероятно, лучше всего для всех.

Наконец, это также работает с инициализаторами объектов:

public class Baz
{
    public String Name { get; set; }
}

public class Bar
{
    public Foo Foo { get; } = new Foo { 1000 };
    public Baz Baz { get; } = new Baz { Name = "Initial name" };
}

Итак...

var b = new Bar { Foo = { 0, "Beringia" }, Baz = { Name = "Arbitrary" } };

Что на самом деле превращается в...

var b = new Bar();

b.Foo.Add(0);
b.Foo.Add("Beringia");
b.Baz.Name = "Arbitrary";

Мы не можем инициализировать Bar.Baz, потому что у него нет setter, но мы можем инициализировать его свойства так же, как мы можем инициализировать элементы в Foo. И это правда, даже если они уже были инициализированы другим инициализатором объекта, прикрепленным к фактическому конструктору.

Инициализаторы коллекции, как и следовало ожидать, являются кумулятивными: Bar.Foo будет иметь три элемента: { "1000", "0", "Beringia" }.

Когда вы думаете о фигурных скобках как сокращении для столбца операторов присваивания или Add() перегрузки вызовов, все это фокусируется.

Но я согласен, что знак равенства jarring в случаях, когда lvalue на самом деле не назначается.

Bonus

Вот еще одна функция сопоставления шаблонов, которую я узнал о из этой статьи Эрика Липперта:

public static class HoldMyBeerAndWatchThis
{
    public static IEnumerable<int> Select(Func<String, String> f)
    {
        yield return f("foo").Length;
    }
}

Поэтому...

var x = from s in HoldMyBeerAndWatchThis select s;

Все, что вам нужно для select, чтобы работать, - это то, что вещь, которую вы выбираете, должна иметь метод с именем select, который возвращает что-то такое, что quacks как IEnumerable, как указано в примечаниях @EricLippert на foreach в связанная статья (спасибо Эрик!) и принимает параметр Func<T,T>.

Ответ 2

Похоже, в двух других ответах есть путаница относительно того, как это работает. Я отсылаю вас к разделу спецификации С# об инициализаторах объектов, в котором четко суммируется семантика:

Инициализатор членов, который задает инициализатор коллекции после знака равенства, является инициализацией встроенной коллекции. Вместо того, чтобы назначать новую коллекцию для поля или свойства, элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылаются поле или свойство.

Помните, что спецификация опубликована для вашего удобства; если у вас есть вопрос о значении языка языка С#, это (или печатная аннотированная версия "Язык программирования С#" ) должна быть вашей первой ссылкой.

Ответ 3

Не всегда. О чем вы думаете:

int[] array = new int[] { 1, 2, 3, 4 };

Это инициализатор массива. Однако у вас также есть:

SomeObject obj = new SomeObject { Name = "Hi!", Text = "Some text!" };

Это инициализатор объекта. То, что у вас есть в вашем "замещающем" сломанном коде, - это совсем другое - инициализатор коллекции. Это работает с любым типом, который реализует IEnumerable и имеет метод Add с правильными аргументами (в данном случае что-то вроде public void Add(View view)).

SomeList list = new SomeList { "Hi!", "There!" };

В вашем случае используются последние два, а инициализатор коллекции не создает экземпляр новой коллекции. Простой пример кода:

void Main()
{
  var some = new SomeObject { List = { "Hi!", "There!" } };

  some.List.Dump();
}

public class SomeObject
{
  public List<string> List { get; private set; }

  public SomeObject()
  {
    List = new List<string>();
  }
}

В этом случае код в Main примерно соответствует этому эквивалентному С# -коду:

var some = new SomeObject();
some.List.Add("Hi!");
some.List.Add("There!");

Эта форма действительна только внутри инициализатора объекта, но она разработана специально для случая, когда у вас есть поле readonly, которое необходимо инициализировать, используя синтаксис инициализатора объекта/коллекции. Например, это не работает:

var some = new SomeObject();
some.List = { "Hi!", "There!" };

Если компилятор использовал тот же "трюк", что и раньше, он мог добавлять только элементы в коллекцию, но он не гарантирует, что коллекция пуста (хотя даже в случае инициализатора объекта это только "гарантия от соглашение" - если вы сначала инициализируете список некоторыми элементами, они останутся, когда произойдет "назначение" ).

Что все, люди:)