Откуда берутся элементы, связанные со списком?

Может быть, это глупый (или более чем тривиальный) вопрос, но, похоже, я просто не знаю ответа. Вот случай -

  1. Я назначил UserList в качестве ItemsSource поля со списком. Итак, что я сделал, по сути, присвоил ссылочный тип другому.
  2. Я очистил UserList. Итак, теперь я также получаю Count из ItemsSource 0.
  3. Я все еще получаю элементы, присутствующие в моем поле со списком. И я также могу привести SelectedItem поля со списком к объекту User.

Вот полный код -

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public partial class MainWindow : Window
{
    private List<User> _userList;

    public MainWindow()
    {
        InitializeComponent();
        _userList = new List<User>()
                                  {
                                      new User() {Id = 1, Name = "X"},
                                      new User() {Id = 2, Name = "Y"},
                                      new User() {Id = 3, Name = "Z"}
                                  };
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        this.comboBox1.ItemsSource = _userList;
        this.comboBox1.DisplayMemberPath = "Name";
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        _userList.Clear();

        /* ItemsSource is cleared as well*/
        IEnumerable userList = this.comboBox1.ItemsSource;

        /*I can still get my User*/
        User user = this.comboBox1.SelectedItem as User;
    }
}  

Итак, откуда берутся предметы? Что на самом деле происходит под капотом, когда я делаю такую ​​привязку? Есть ли у элемента управления какой-то кеш? Королевская боль осознавать, что у тебя нет таких базовых идей. Кто-нибудь может объяснить закулисные детали?

EDIT: я написал код в WPF, но у меня есть тот же вопрос для WinForms Combobox.

EDIT: Разве поле со списком не отображает элементы из памяти Datasource? Когда этот источник данных содержит 0 элементов, как он отображает элементы?


person atiyar    schedule 04.09.2012    source источник
comment
Это WinForms или WPF? Не может быть и того и другого!   -  person Dan Puzey    schedule 04.09.2012
comment
@DanPuzey: В целом это Combobox, и то, как он все еще удерживает элементы после очистки источника данных.   -  person atiyar    schedule 04.09.2012
comment
Поле со списком WPF и WinForms — это совершенно разные реализации, и они по-разному обрабатывают данные или привязку. Я не думаю, что в поле со списком WinForms есть ItemsSource, поэтому я предполагаю, что это WPF?   -  person Dan Puzey    schedule 04.09.2012
comment
@DanPuzey: да, я написал код в WPF, но у меня тот же вопрос в случае WinForms   -  person atiyar    schedule 04.09.2012
comment
Вы должны четко указать в своем вопросе, что это так - ваш опубликованный код недействителен WinForms. (Для начала ComboBox в WinForms не имеет свойства ItemsSource.) Я бы посоветовал вам либо опубликовать сопоставимый код для WinForms, либо соответствующим образом ограничить свой вопрос. Если вы предполагаете, что ответ один и тот же для WinForms и WPF, вы только усугубите путаницу, потому что они совершенно разные.   -  person Dan Puzey    schedule 04.09.2012


Ответы (4)


Когда вы устанавливаете ItemsSource любого ItemsControl, он копирует ссылку на список в свое свойство Items. Затем он подписывается на событие OnCollectionChanged и создает объект CollectionView. Итак, на экране вы видите этот collectionView.

как я нашел в исходном коде, ItemCollection содержит два списка:

internal void SetItemsSource(IEnumerable value)
    {
      //checks are missed
      this._itemsSource = value;
      this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
    }

Как можно получить SelectedItem?

Это мое предположение из быстрого просмотра исходного кода:

ItemsControl имеет набор "представлений", и каждый View должен хранить ссылку на элемент (экземпляр User), поскольку он должен отображать данные на экране. Итак, когда вы вызываете SelectedItem, он возвращает сохраненную ссылку.

Обновление ссылок

Предположим, что есть экземпляр User. Он имеет адрес 123 в памяти. Есть список. Он хранит ссылки. Один из них 123.

При установке ItemsSource ItemsControl сохраняет ссылку на список и создает коллекцию представлений. Каждое представление хранит ссылки на элемент. Одно представление хранит адрес 123.

Затем вы очистили список пользователей. Теперь список не содержит ссылок на Users. Но в памяти есть адрес 123 и есть экземпляр User по этому адресу. Сборщик мусора не уничтожает его, потому что View имеет ссылку на него.

Когда вы получаете SelectedItem, он возвращает экземпляр пользователя с адреса 123.

var user = new User();

var list = new List<User>();
list.Add(user);

list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty

Console.WriteLine(user == null); //prints false. - user instance is sill exists;
person Anton Sizikov    schedule 04.09.2012
comment
Антон, пожалуйста, смотрите EDIT. я подумал, что лучше быть с самим вопросом, чем в комментарии. - person atiyar; 04.09.2012
comment
На экране вы можете увидеть вид ваших предметов. ComboBox не знает, что он должен обновляться. - person Anton Sizikov; 04.09.2012
comment
Антон прав. ComboBox не может знать, что содержимое List‹› изменилось. Либо назначьте новый пустой список для ItemsSource, либо лучше используйте ObservableCollection, как предлагает Антон. - person GazTheDestroyer; 04.09.2012
comment
@AntonSizikov: Если он копирует ссылки, то есть мой вопрос. Все они относятся к одной и той же ячейке памяти, и я просто очистил их. И если это только представление, которое содержит элементы, то как я могу преобразовать SelectedItem обратно в User в коде? - person atiyar; 04.09.2012
comment
@GazTheDestroyer: на самом деле мне не нужно очищать ComboBox, я пытаюсь понять, почему он не очищается и как он удерживает предметы? - person atiyar; 04.09.2012
comment
@NerotheZero: Представьте, что вы сами реализуете комбо. Когда кто-то устанавливал ваше свойство ItemsSource, вы перебирали его и рисовали каждый элемент на экране. Если клиентский код каким-то образом изменил содержимое, как бы вы узнали? Все, что у вас есть, это ссылка, которая не изменилась. Единственный способ - постоянно повторять сбор, что было бы крайне неэффективно. - person GazTheDestroyer; 04.09.2012
comment
@NerotheZero: см. мой новый ответ для уточнения вашего нового вопроса. - person Dan Puzey; 04.09.2012
comment
@NerotheZero добавил некоторые детали - person Anton Sizikov; 04.09.2012
comment
@GazTheDestroyer: я попрошу вас опубликовать ответ, поясняющий, что вы имеете в виду, пожалуйста. Я не понимаю, как ссылка не изменилась после того, как я позвонил Clear в UserList. они относятся к одной и той же ячейке памяти, верно? и, как я прокомментировал Антону, если это только элементы, нарисованные в представлении, как я могу вернуть элемент обратно в User? - person atiyar; 04.09.2012
comment
@NerotheZero я ответил на твой последний вопрос? - person Anton Sizikov; 04.09.2012
comment
@AntonSizikov: вы всегда говорите, что это ссылка, так как она копирует ссылку в список, и каждый вид должен хранить ссылку на элемент. если это ссылка (которая представляет собой не что иное, как адрес памяти), то там нет элемента, потому что я только что очистил его через другую ссылку, _userList. я ошибся? - person atiyar; 04.09.2012
comment
@AntonSizikov: большое спасибо за помощь. Можете ли вы дать что-нибудь, где я могу найти больше об этом копировании ссылки в список в его свойство Items? - person atiyar; 04.09.2012
comment
Что бы вы хотели увидеть? Он просто сохраняет пройденную коллекцию. - person Anton Sizikov; 04.09.2012

В ответ на ваш комментарий к @GazTheDestroyer («... почему он не очищается и как он удерживает предметы?»)

В WPF, когда вы устанавливаете свойство ItemsSource для ItemsControl, элемент управления будет заключать список элементов в CollectionView, который является типом коллекции, оптимизированным для использования инфраструктурой пользовательского интерфейса. Это CollectionView присваивается свойству Items элемента управления, и именно с ним фактически работает код отображения. Как видите, эта коллекция полностью отделена от объекта, который вы изначально присвоили ItemsSource, поэтому изменения не передаются от одного к другому. Вот почему элементы остаются в элементе управления, когда вы очищаете исходный список: элемент управления игнорирует исходный список и имеет свой собственный список, содержащий ваши объекты.

Именно по этой причине значение ItemsSource должно вызывать события, особенно INotifyCollectionChanged.NotifyCollectionChanged, чтобы элемент управления знал, что нужно обновить список Items. ObservableCollection реализует этот интерфейс и вызывает правильное событие, поэтому функциональность работает должным образом.

Чрезвычайно важно отметить, что это ничего не похоже на то, что происходит в WinForms, поэтому я настаивал на разъяснении.

РЕДАКТИРОВАНИЕ: поясняю, что "глубокой копии" не существует. Код, который происходит, в принципе похож на следующий:

private List<object> myCopy;

public void SetItemsSource(List<object> yourCopy)
{
     myCopy = new List<object>();
     foreach (var o in yourCopy)
     {
         myCopy.Add(o);
     }
}

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

person Dan Puzey    schedule 04.09.2012
comment
Вы говорите, что ItemsControl выполняет глубокую копию для объекта, назначенного ему ItemsSource, а не просто указывает на его ссылку или копирует ссылку в список, как упоминал Антон? потому что только это может объяснить такое поведение. - person atiyar; 04.09.2012
comment
ваша... эта коллекция полностью отделена от объекта, который вы изначально назначили ItemsSource, и поэтому нет передачи изменений от одного к другому... указывает, что здесь происходит что-то вроде глубокого копирования. - person atiyar; 04.09.2012
comment
Нет, это не глубокая копия, а просто эталонная копия. Если вы передадите мне объект A, а я напишу object b = A, то у меня будет собственная ссылка на ваш объект. Если вы впоследствии удалите A из списка, о котором я ничего не знаю, это никак не повлияет на меня. Очистка вашего списка ничего не делает с A, но удаляет ссылку на него. - person Dan Puzey; 04.09.2012
comment
+1 за то, что код в EDIT был полезен, чтобы исправить мой взгляд на список ссылочного типа. большое спасибо :) и где я могу найти еще кое-что об этом заключении списка элементов в CollectionView и назначении его Items? - person atiyar; 04.09.2012

Предполагая, что вы используете WPF:

List<User> не запускает никаких событий, которые пользовательский интерфейс распознает для обновления. Если вместо этого вы используете ObservableCollection<User>, ваш код будет работать.

Ключевое отличие состоит в том, что ObservableCollection реализует INotifyCollectionChanged, что позволяет пользовательскому интерфейсу распознавать, что содержимое коллекции изменилось, и, таким образом, обновлять содержимое ComboBox.

(Обратите внимание, что это не работает в WinForms. В WinForms вы можете установить свойство DataSource элемента управления, но тот же прием ObservableCollection здесь не работает.)

person Dan Puzey    schedule 04.09.2012

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

Он перечислит ссылку и отобразит элементы. Делает ли он глубокую или поверхностную копию, не имеет значения, все, что у него есть, это ссылка (фактически адрес памяти).

Если вы каким-то образом измените свою коллекцию, комбо не сможет об этом узнать, если вы как-нибудь не сообщите об этом. Референс (адрес) не изменился, до комбо все выглядит так же. Вы, кажется, думаете, что объект как-то "живой" и комбо может наблюдать за изменением памяти или что-то в этом роде? Это не так. Все, что у него есть, это ссылка, которую он может перечислить. Содержимое может измениться, но без какого-либо триггера комбо об этом не знает и поэтому будет сидеть и ничего не делать.

ObservableCollection предназначен для преодоления этого. Он реализует INotifyCollectionChanged, который запускает события при его изменении, поэтому Combo знает, что он должен обновить свой дисплей.

person GazTheDestroyer    schedule 04.09.2012