Наткнулся на очень интересное расширение для LINQ позволяющее декларативно работать с асинхронным интерфейсом (
themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html ). Сейчас расширение существует как часть Silverlight Toolkit (Rx framework), но в последствии станет частью .NET 4.0 (System.Reactive.dll).
Идея в том, чтобы стереть границы между IEnumerable и IObservable интерфейсами (читай между Iterator и Observer паттернами). Асинхронные события представляются как последовательность данных, возвращаемых будто бы по yield return. А это в свою очередь позволяет работать с данной последовательностью через LINQ в функциональном, декларативном стиле.
Например, можно использовать LINQ для формулирования выражений о перетаскивании контролов:
IObservable<Event<MouseEventArgs>> draggingEvent =
from mouseLeftDownEvent in control.GetMouseLeftDown()
from mouseMoveEvent in control.GetMouseMove().Until(control.GetMouseLeftUp())
select mouseMoveEvent;
Выражение гласит следующее: для каждого нажатия на левую кнопку мыши получить каждое событие перемещения мыши до тех пор, пока левая кнопка мыши не будет отжата.
Таким образом from позволяет декларативно описывать последовательность событий. В противном случае, потребовалось бы создавать конечный автомат и устанавливать флаг нажатия на левую кнопку мыши. А в обработчике перемещения мыши тестировать этот флаг и изменять поведение обработчика в зависимости от него. Использование же Rx фреймворка позволяет описать последовательность событий декларативно и не вводить дополнительные переменные.
Подобным образом можно тестировать GUI где переходы из состояния в состояние не мгновенны, а происходят с анимацией, окончания которой необходимо дождаться прежде чем производить обращения к свойствам контрола.
Rating rating = new Rating();
IObservable<Unit> test =
ObservableExtensions
.DoAsync(() => TestPanel.Children.Add(rating)) // добавить контрол на панель
.WaitFor(TestPanel.GetLayoutUpdated()) // подождать пока появится
.DoAsync(() => rating.Value = 1.0) // установить свойство
.WaitFor(
rating
.GetActualValueChanged() // ждать пока не установится
.SkipWhile(actualValueChangedEvent => // новое значение свойства
actualValueChangedEvent.EventArgs.NewValue != rating.Value))
.Assert(() => rating.GetRatingItems().Last().ActualValue == 1.0); // Assert
// очистка ресурсов по завершению теста
test.Subscribe(() => TestPanel.Children.Remove(rating));
Автор оригинального поста обещает в ближайшее время осветить межпоточное взаимодействие с использованием этого фреймворка.
Области применения
- Построение КА для визуального интерфейса пользователя.
- Координирование событий Workflow.
- Координирование сообщений с периферийными устройствами. В том числе с сетью (веб-службами) и с любым асинхронным IO.
- Работа с цепочками событий.
Адаптация под WPF
silverlight.codeplex.com/SourceControl/ListDownloadableCommits.aspx
evain.net/blog/articles/2009/07/30/rebasing-system-reactive-to-the-net-clr
Похоже, ветки кода, адаптированной под WPF, пока нет, но обещают добавить в .NET 4.0. Однако уже сейчас можно взять сырцы и поковырять вручную.
Bindable LINQ
Развитие идеи — осуществление биндинга к декларативно описанным цепочкам событий. При изменении зависимого свойства, GUI перерисовывается чтобы отразить изменения. В Rx этого сейчас нет. Однако подобная связь с INotifyPropertyChanged есть в следующих проектах (сами проекты пока не смотрел):