Что такое ObservableCollection и когда её использовать?

ObservableCollection<T> — это коллекция из пространства имён System.Collections.ObjectModel, которая представляет собой динамический список объектов, автоматически уведомляющий интерфейс пользователя об изменениях: добавлении, удалении или обновлении элементов. Это делает её крайне полезной и практически стандартной для работы с коллекциями данных в архитектуре MVVM в приложениях на Xamarin.Forms.

Основные характеристики ObservableCollection

  1. Поддерживает уведомление об изменениях — реализует интерфейс INotifyCollectionChanged.

  2. Работает с привязкой данных (Data Binding) — изменения в коллекции автоматически отображаются в UI, например в ListView, CollectionView и других элементах.

  3. Реализует IList<T> и IEnumerable<T> — с коллекцией можно работать как со списком.

  4. Удобна для MVVM-подхода — позволяет ViewModel уведомлять View о добавлении/удалении элементов без дополнительных действий.

Когда использовать ObservableCollection

  • Когда вы хотите отображать список элементов в интерфейсе, который может динамически меняться.

  • Когда необходимо, чтобы UI автоматически обновлялся при изменении коллекции.

  • При использовании двусторонней привязки (two-way binding) к элементам, как ListView, CollectionView, Picker, ListBox и т.д.

  • Когда нужно реализовать добавление, удаление, обновление элементов из ViewModel, сохраняя синхронность с UI.

Пример использования в ViewModel

using System.Collections.ObjectModel;
using System.ComponentModel;
public class Contact
{
public string Name { get; set; }
public string Phone { get; set; }
}
public class ContactsViewModel : INotifyPropertyChanged
{
public ObservableCollection&lt;Contact&gt; Contacts { get; }
public ContactsViewModel()
{
Contacts = new ObservableCollection&lt;Contact&gt;
{
new Contact { Name = "Анна", Phone = "123-456" },
new Contact { Name = "Иван", Phone = "987-654" }
};
}
public event PropertyChangedEventHandler PropertyChanged;
}

Привязка к XAML

&lt;ContentPage.BindingContext&gt;
&lt;local:ContactsViewModel /&gt;
&lt;/ContentPage.BindingContext&gt;
&lt;ListView ItemsSource="{Binding Contacts}"&gt;
&lt;ListView.ItemTemplate&gt;
&lt;DataTemplate&gt;
&lt;TextCell Text="{Binding Name}" Detail="{Binding Phone}" /&gt;
&lt;/DataTemplate&gt;
&lt;/ListView.ItemTemplate&gt;
&lt;/ListView&gt;

Когда вы добавите или удалите элементы из Contacts, ListView обновится автоматически.

Работа с ObservableCollection

Добавление:

Contacts.Add(new Contact { Name = "Мария", Phone = "555-123" });

Удаление:

var contactToRemove = Contacts.FirstOrDefault(c => c.Name == "Анна");
if (contactToRemove != null)
Contacts.Remove(contactToRemove);

Очистка:

Contacts.Clear();

Как это работает под капотом

Класс ObservableCollection<T> реализует интерфейс INotifyCollectionChanged и генерирует событие CollectionChanged, которое отслеживается элементами управления (например, ListView).

Это событие содержит информацию о:

  • Типе изменения (Add, Remove, Reset, Replace, Move)

  • Элементах, участвующих в изменении

  • Индексах изменений

Благодаря этому фреймворк Xamarin.Forms или WPF может частично обновлять UI, не перерисовывая весь список.

Ограничения ObservableCollection

Не уведомляет об изменении свойств объектов внутри коллекции
Если у вас есть объект Contact и вы изменяете Name, ListView не отреагирует на это изменение, если Contact не реализует INotifyPropertyChanged.

Решение:

```python
public class Contact : INotifyPropertyChanged
{
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
public string Phone { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}

**Не потокобезопасна  
**Изменения ObservableCollection из другого потока (например, при асинхронной загрузке) вызывают исключения. Добавлять/удалять элементы можно **только из UI-потока**.  
<br/>Решение: использовать Device.BeginInvokeOnMainThread() или MainThread.InvokeOnMainThreadAsync() из Xamarin Essentials:  
<br/>```python  
Device.BeginInvokeOnMainThread(() =>
{
Contacts.Add(new Contact { Name = "Новый", Phone = "000-000" });
});  

Нет поддержки сортировки или фильтрации
Чтобы отсортировать коллекцию, нужно создавать новую ObservableCollection:

```python
var sorted = Contacts.OrderBy(c => c.Name).ToList();
Contacts.Clear();
foreach (var item in sorted)
Contacts.Add(item);

### **ObservableCollection vs List**

| **Свойство** | **List&lt;T&gt;** | **ObservableCollection&lt;T&gt;** |
| --- | --- | --- |
| Уведомляет UI об изменениях |  Нет |  Да |
| --- | --- | --- |
| Поддержка Data Binding |  Ограниченная |  Полная |
| --- | --- | --- |
| Подходит для MVVM |  Не рекомендуется |  Рекомендуется |
| --- | --- | --- |
| Производительность |  Быстрее |  Медленнее при больших объёмах |
| --- | --- | --- |

### **Альтернативы и расширения**

- **BindingList&lt;T&gt;**  устаревший тип из WinForms, поддерживает уведомления, но менее эффективен и не рекомендуется в новых проектах.  

- **ReadOnlyObservableCollection&lt;T&gt;**  только для чтения (без возможности добавлять извне).  

- **Расширенные коллекции** (через сторонние библиотеки, например DynamicData, MvvmCross.ObservableCollections)  поддержка фильтрации, сортировки, буферизации и др.  


### **Пример расширенного использования**

Допустим, вы хотите добавить кнопку, при нажатии на которую в список добавляется новый элемент.

#### **ViewModel:**

```python  
public ICommand AddContactCommand { get; }
public ContactsViewModel()
{
Contacts = new ObservableCollection&lt;Contact&gt;();
AddContactCommand = new Command(() =>
{
Contacts.Add(new Contact { Name = "Новый контакт", Phone = "000-000" });
});
}

XAML:

<Button Text="Добавить контакт"
Command="{Binding AddContactCommand}" />

ObservableCollection<T> — это мощный инструмент, позволяющий строить динамические, интерактивные пользовательские интерфейсы в приложениях Xamarin.Forms. Она играет центральную роль в реализации шаблона MVVM, обеспечивая синхронизацию между представлением (View) и моделью (ViewModel) без необходимости вручную обновлять интерфейс.