AI Core — реактивность
AI-ориентированная карта PropertyBinder, Rx/ReactiveUI, DynamicData, реактивных коллекций и объектных привязок, используемых в коде в стиле EyeAuras.
Карта реактивности
Эта карта описывает общий слой реактивности, который используется в коде приложения, представлениях Blazor/WPF, долгоживущих сервисах и script mini-apps.
Используйте её, когда коду нужны вычисляемые свойства, живые коллекции, отслеживание изменений, обновление UI от состояния или подписки, которые должны освобождаться вместе с объектом-владельцем.
Когда это нужно
- Пересчитывать одно свойство при изменении других свойств.
- Держать состояние UI синхронизированным с состоянием сервиса или модели.
- Подписываться на события или изменения значений без утечек обработчиков.
- Поддерживать живую таблицу, список или дерево на основе меняющихся данных.
- Фильтровать, сортировать, преобразовывать или группировать живую коллекцию.
- Условно привязывать значения, включая
nullи fallback-сценарии. - Переносить реактивные обновления на UI scheduler/dispatcher.
- Понимать, когда использовать
PropertyBinder, Rx-подписки или DynamicData.
Модель концепций
DisposableReactiveObject— обычная базовая модель для объектов, которым нужно управление временем жизни и/или уведомления об изменении свойств. Подписки и ресурсы, которыми владеет объект, добавляйте вAnchors.- Rx (
IObservable<T>) — это слой потоков. Используйте его для событий, таймеров, асинхронного состояния, throttling, переключения, комбинирования и разовых подписок. - ReactiveUI даёт хелперы для наблюдения за свойствами, например
WhenAnyValue,WhenAnyProperty,ReactiveCommandиObservableAsPropertyHelper. PropertyBinderнужен для декларативных привязок на уровне объекта. Обычно его объявляют как статическийBinder<TContext>и подключают один раз из конструктора экземпляра.- DynamicData — предпочтительный слой для изменяемых живых коллекций.
SourceList<T>иSourceCache<T, TKey>отдают наборы изменений черезConnect(). SourceCache<T, TKey>обычно лучше подходит, когда у элементов есть стабильные ID.SourceList<T>удобнее для упорядоченных списков без ключа.SourceListEx<T>иSourceCacheEx<T, TKey>— это обёртки PoeShared, которые владеют источником DynamicData и отдают материализованные read-only коллекции.- UI-код обычно работает с материализованными
IReadOnlyObservableCollection<T>, полученными черезBindToCollection(...)или похожие хелперы. - Свойства, которыми владеет UI, должны обновляться на правильном scheduler/dispatcher. Для этого используйте scheduler-aware хелперы binder-а или
ObserveOn(...), где это нужно.
Основные API
PropertyBinder.Binder<TContext>- хранит декларативные правила привязки для типа контекста.Binder.Attach(instance)- активирует все правила binder-а для конкретного объекта; освобождайте результат вместе со временем жизни объекта.Binder.Bind(...)- привязывает выражение к целевому свойству.Binder.BindIf(...).ElseIf(...).Else(...)- цепочка условной привязки.To(...)- задаёт целевое свойство или вызывает callback присваивания.WithDependency(...)- добавляет дополнительные сигналы зависимостей, которые должны запускать пересчёт.PropagateNullValues(...)- управляет поведением распространенияnullв путях свойств.DoNotRunOnAttach()- отключает начальное вычисление привязки при attach.DoNotOverride()/OverrideKey(...)- управляет тем, как правила привязки переопределяют друг друга.OnScheduler(...)- выполняет присваивание binder-а на выбранном scheduler.WhenAnyValue(...)- наблюдает одно или несколько значений свойств.WhenAnyProperty(...)- наблюдает уведомления об изменении свойств по имени свойства или выражению.ReactiveCommand- абстракция команды с наблюдаемым состоянием выполнения.ObservableAsPropertyHelper<T>- хранит вычисленное observable-значение как свойство.SourceList<T>- изменяемый живой список.SourceCache<T, TKey>- изменяемая живая коллекция с ключом.Connect()- открывает поток change set для DynamicData.Preview()- наблюдает изменения до того, как они будут применены.Edit(...)- пакетно изменяет источник DynamicData.Watch(key)- наблюдает один элемент вSourceCache.Lookup(key)/GetOrDefault(...)- читает значения из keyed cache.CountChanged- наблюдает текущее количество элементов в живой коллекции.BindToCollection(out collection)- материализует change set в read-only observable collection.ToObservableChangeSet()- адаптирует observable sequence или коллекцию в change set для DynamicData.SourceListEx<T>/SourceCacheEx<T, TKey>- обёртки PoeShared вокруг источников DynamicData с учётом времени жизни.BindableReactiveObject/ReactiveBinding- инфраструктура привязки по путям свойств для динамических сценариев source/target binding.
Паттерн PropertyBinder
Используйте Binder<T> для стабильного вычисляемого состояния на уровне объекта. Не создавайте binder-ы внутри render loop, polling loop или кода, который выполняется каждый frame.
private sealed class CharacterState : DisposableReactiveObject
{
private static readonly Binder<CharacterState> Binder = new();
static CharacterState()
{
Binder.Bind(x => $"{x.Name} ({x.Level})")
.To(x => x.DisplayName);
Binder.BindIf(x => x.Target != null, x => x.Target.Name)
.Else(x => "<no target>")
.To(x => x.TargetName);
}
public CharacterState()
{
Binder.Attach(this).AddTo(Anchors);
}
public string Name { get; set; } = "";
public int Level { get; set; }
public TargetInfo? Target { get; set; }
public string DisplayName { get; private set; } = "";
public string TargetName { get; private set; } = "";
}
Паттерн Rx-подписок
Используйте Rx для потоков и side effect-ов. Всегда освобождайте подписки вместе с объектом-владельцем.
this.WhenAnyValue(x => x.IsRunning)
.DistinctUntilChanged()
.Subscribe(isRunning => Log.Info($"Running: {isRunning}"), Log.HandleException)
.AddTo(Anchors);
Часто используемые операторы:
Select- преобразует значение.Where- фильтрует события.DistinctUntilChanged- убирает повторяющиеся значения.CombineLatest- объединяет несколько последних значений.Switch- использует последний внутренний observable и отписывается от предыдущего.Throttle/Sample- уменьшает шум в потоке событий.ObserveOn- переносит downstream-уведомления на нужный scheduler.
Паттерн DynamicData
Используйте DynamicData, когда коллекция меняется со временем. Предпочитайте пайплайны change set вместо ручного сравнения списков.
private readonly SourceCache<ItemVm, string> items = new(x => x.Id);
public IReadOnlyObservableCollection<ItemVm> Items { get; }
public InventoryVm()
{
items.Connect()
.Filter(x => x.IsVisible)
.BindToCollection(out var visibleItems)
.Subscribe()
.AddTo(Anchors);
Items = visibleItems;
items.AddTo(Anchors);
}
Для пакетных изменений используйте Edit(...):
items.Edit(update =>
{
update.Clear();
update.AddOrUpdate(newItems);
});
Что и когда использовать
- Используйте
PropertyBinderдля долгоживущих вычисляемых свойств и проекций состояния объекта. - Используйте
WhenAnyValue/ Rx для потоков событий, таймеров, подписок, debounce/throttle-логики, асинхронного переключения и side effect-ов. - Используйте
DynamicDataдля живых коллекций, особенно таблиц, деревьев, кэшей и данных, которые постоянно обновляются. - Используйте обычные свойства для простого локального состояния, которому не нужны уведомления и вычисляемые обновления.
- Используйте
ReactiveCommand, когда UI-действиям нужно наблюдаемое состояние выполнения, ошибки или логика can-execute. - Используйте
SourceCache<T, TKey>для списков сущностей, списков процессов, окон, aura objects, файлов и любых данных со стабильными ID. - Используйте
SourceList<T>, когда порядок сам по себе является моделью и естественного ключа нет.
Чего не делать
- Не оставляйте Rx-подписки без освобождения. Добавляйте их в
Anchors. - Не изменяйте UI-bound свойства из произвольных background thread.
- Не выносите
Binder.Attach(...)за пределы владельца времени жизни объекта. - Не создавайте
Binder<T>заново много раз для одного и того же типа. - Не делайте ручной diff для
List<T>, если DynamicData может отдать change set. - Не полагайтесь на обычные auto-property, если потребители ожидают уведомления об изменении свойств.
- Не создавайте циклические reactive binding, если они не защищены очень аккуратно.
- Не прячьте долгие операции внутри getter-ов свойств или binding-выражений.
Синонимы для поиска
- reactivity, reactive state, property changed, INPC, notify property changed
- PropertyBinder, binder, derived property, computed property, conditional binding
- Rx, System.Reactive, observable, subscription, scheduler, ObserveOn
- ReactiveUI, WhenAnyValue, ReactiveCommand, ObservableAsPropertyHelper
- DynamicData, SourceList, SourceCache, change set, live collection
- BindToCollection, observable collection, filtered table, sorted list
- AddTo Anchors, lifetime, Dispose, DisposableReactiveObject
Опорные точки для исследования
Binder<TContext>PropertyRuleBuilderBindBindIfElseIfElseToWithDependencyPropagateNullValuesOnSchedulerWhenAnyValueWhenAnyPropertyReactiveCommandObservableAsPropertyHelperSourceList<T>SourceCache<T, TKey>ConnectPreviewEditWatchLookupCountChangedBindToCollectionToObservableChangeSetSourceListEx<T>SourceCacheEx<T, TKey>BindableReactiveObjectReactiveBindingReactiveTrackerListReactiveSection