C# 使用IObservable(Rx)作为iNotifyCollection替换MVVM?

C# 使用IObservable(Rx)作为iNotifyCollection替换MVVM?,c#,silverlight,mvvm,system.reactive,C#,Silverlight,Mvvm,System.reactive,我一直在研究在MVVM框架中使用Rx。其思想是使用内存数据集上的“实时”LINQ查询将数据投影到视图模型中,以便与之绑定 以前,通过使用INotifyPropertyChanged/INotifyCollectionChanged和一个名为。Rx和IObservable的潜力在于使用Subject类将更改的事件从源模型传播到视图,从而移动到更具声明性的ViewModel。最后一步需要从IObservable转换到常规数据绑定接口 问题是Rx似乎不支持实体已从流中删除的通知。下面的示例。 代码显示

我一直在研究在MVVM框架中使用Rx。其思想是使用内存数据集上的“实时”LINQ查询将数据投影到视图模型中,以便与之绑定

以前,通过使用INotifyPropertyChanged/INotifyCollectionChanged和一个名为。Rx和IObservable的潜力在于使用Subject类将更改的事件从源模型传播到视图,从而移动到更具声明性的ViewModel。最后一步需要从IObservable转换到常规数据绑定接口


问题是Rx似乎不支持实体已从流中删除的通知。下面的示例。
代码显示了一个POCO,它使用BehaviorSubject类作为字段状态。代码继续创建这些实体的集合,并使用Concat将过滤器流合并在一起。这意味着对POCO的任何更改都将报告给单个流

此流的筛选器设置为筛选评级==0。当偶数发生时,订阅仅将结果输出到调试窗口

任何元素的设置等级=0都将触发事件。但将评级设置回5将不会看到任何事件

在CLINQ的情况下,查询的输出将支持INotifyCollectionChanged,因此从查询结果中添加和删除的项将触发正确的事件,以指示查询结果已更改(添加或删除的项)

我想到这个地址的唯一方法是设置两个带有反向站点(双)查询的流。添加到相反流的项意味着从结果集中删除。如果做不到这一点,我可以只使用FromEvent,而不让任何实体模型可见——这使得Rx更像是一个事件聚合器。有什么建议吗

using System;
using System.ComponentModel;
using System.Linq;
using System.Collections.Generic;

namespace RxTest
{

    public class TestEntity : Subject<TestEntity>, INotifyPropertyChanged
    {
        public IObservable<string> FileObservable { get; set; }
        public IObservable<int> RatingObservable { get; set; }

        public string File
        {
            get { return FileObservable.First(); }
            set { (FileObservable as IObserver<string>).OnNext(value); }
        }

        public int Rating
        {
            get { return RatingObservable.First(); }
            set { (RatingObservable as IObserver<int>).OnNext(value); }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public TestEntity()
        {
            this.FileObservable = new BehaviorSubject<string>(string.Empty);
            this.RatingObservable = new BehaviorSubject<int>(0);
            this.FileObservable.Subscribe(f => { OnNotifyPropertyChanged("File"); });
            this.RatingObservable.Subscribe(f => { OnNotifyPropertyChanged("Rating"); });
        }

        private void OnNotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
            // update the class Observable
            OnNext(this);
        }

    }

    public class TestModel
    {
        private List<TestEntity> collection { get; set; }
        private IDisposable sub;

        public TestModel()
        {
            this.collection = new List<TestEntity>() {
            new TestEntity() { File = "MySong.mp3", Rating = 5 },
            new TestEntity() { File = "Heart.mp3", Rating = 5 },
            new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }};

            var observableCollection = Observable.Concat<TestEntity>(this.collection.Cast<IObservable<TestEntity>>());
            var filteredCollection = from entity in observableCollection
                                     where entity.Rating==0
                                     select entity;
            this.sub = filteredCollection.Subscribe(entity =>
                {
                    System.Diagnostics.Debug.WriteLine("Added :" + entity.File);
                }
            );
            this.collection[0].Rating = 0;
            this.collection[0].Rating = 5;
        }
    };
}
使用系统;
使用系统组件模型;
使用System.Linq;
使用System.Collections.Generic;
名称空间RxTest
{
公共类测试实体:主题,InotifyProperty已更改
{
public IObservable FileObservable{get;set;}
公共IObservable比率可观测{get;set;}
公共字符串文件
{
获取{return FileObservable.First();}
设置{(FileObservable作为IObserver).OnNext(值);}
}
公共整数评级
{
获取{return RatingObservable.First();}
设置{(RatingObservable as IObserver).OnNext(value);}
}
公共事件属性更改事件处理程序属性更改;
公共测试()
{
this.FileObservable=新的行为主体(string.Empty);
this.RatingObservable=新行为主体(0);
this.FileObservable.Subscribe(f=>{OnNotifyPropertyChanged(“File”);});
this.RatingObservable.Subscribe(f=>{OnNotifyPropertyChanged(“Rating”);});
}
私有void OnNotifyPropertyChanged(字符串属性)
{
如果(PropertyChanged!=null)PropertyChanged(这是新的PropertyChangedEventArgs(property));
//更新可观察的类
OnNext(本);
}
}
公共类测试模型
{
私有列表集合{get;set;}
私人可识别子系统;
公共测试模型()
{
this.collection=新列表(){
new TestEntity(){File=“MySong.mp3”,Rating=5},
new TestEntity(){File=“Heart.mp3”,Rating=5},
新的TestEntity(){File=“KarmaPolice.mp3”,评级=5};
var observateCollection=observatable.Concat(this.collection.Cast());
var filteredCollection=来自observableCollection中的实体
其中entity.Rating==0
选择实体;
this.sub=filteredCollection.Subscribe(实体=>
{
System.Diagnostics.Debug.WriteLine(“添加:“+entity.File”);
}
);
此.collection[0]。评级=0;
此.collection[0]。评级=5;
}
};
}

使用
可观察收集有什么问题?Rx是一个非常容易过度使用的框架;我发现,如果您发现自己在与异步流的基本前提作斗争,那么您可能不应该使用Rx来解决特定的问题。

在我看来,Rx的用法并不合适。可观测的Rx是您可以订阅的“事件”流。您可以对视图模型中的这些事件做出反应,例如,将它们添加到绑定到视图的ObservableCollection中。但是,Observable不能用于表示添加/删除项目的固定项目集。

问题在于,您查看的是来自测试项列表的通知,而不是来自测试项本身。因此,您可以看到添加,但在任何测试中都没有变化。要查看此评论,请执行以下操作:

        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property));

您将看到程序运行的是相同的!您的测试中的通知没有任何关联。正如其他人所说,使用ObservableCollection将为您添加此配线

实际上,我发现反应式UI库对此很有帮助(在NuGet中提供)。 该库包括用于收藏的特殊IObservable主题,以及在传统INCC收藏基础上创建其中一个“反应性收藏”的功能。 通过此功能,我可以在集合中创建新项目、已删除项目和更改项目的流。然后,我使用Zip将流合并在一起,并修改目标ViewModel可观察集合。这将基于对源模型的查询提供实时投影

下面的代码解决了这个问题(这段代码会更简单,但是Silverlight版本的反应式UI存在一些需要解决的问题)。代码只需调整值即可触发集合更改事件
using System;
using System.ComponentModel;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using ReactiveUI;

namespace RxTest
{

    public class TestEntity :  ReactiveObject, INotifyPropertyChanged, INotifyPropertyChanging
    {
        public string _File;
        public int _Rating = 0;
        public string File
        {
            get { return _File; }
            set { this.RaiseAndSetIfChanged(x => x.File, value); }
        }

        public int Rating
        {
            get { return this._Rating; }
            set { this.RaiseAndSetIfChanged(x => x.Rating, value); }
        }

        public TestEntity()
        {
        }
    }

    public class TestModel
    {
        private IEnumerable<TestEntity> collection { get; set; }
        private IDisposable sub;

        public TestModel()
        {
            this.collection = new ObservableCollection<TestEntity>() {
            new TestEntity() { File = "MySong.mp3", Rating = 5 },
            new TestEntity() { File = "Heart.mp3", Rating = 5 },
            new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }};

            var filter = new Func<int, bool>( Rating => (Rating == 0));

            var target = new ObservableCollection<TestEntity>();
            target.CollectionChanged += new NotifyCollectionChangedEventHandler(target_CollectionChanged);
            var react = new ReactiveCollection<TestEntity>(this.collection);
            react.ChangeTrackingEnabled = true;

            // update the target projection collection if an item is added
            react.ItemsAdded.Subscribe( v => { if (filter.Invoke(v.Rating)) target.Add(v); } );
            // update the target projection collection if an item is removed (and it was in the target)
            react.ItemsRemoved.Subscribe(v => { if (filter.Invoke(v.Rating) && target.Contains(v)) target.Remove(v); });

            // track items changed in the collection.  Filter only if the property "Rating" changes
            var ratingChangingStream = react.ItemChanging.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender });
            var ratingChangedStream = react.ItemChanged.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender });
            // pair the two streams together for before and after the entity has changed.  Make changes to the target
            Observable.Zip(ratingChangingStream,ratingChangedStream, 
                (changingItem, changedItem) => new { ChangingRating=(int)changingItem.Rating, ChangedRating=(int)changedItem.Rating, Entity=changedItem.Entity})
                .Subscribe(v => { 
                    if (filter.Invoke(v.ChangingRating) && (!filter.Invoke(v.ChangedRating))) target.Remove(v.Entity);
                    if ((!filter.Invoke(v.ChangingRating)) && filter.Invoke(v.ChangedRating)) target.Add(v.Entity);
                });

            // should fire CollectionChanged Add in the target view model collection
            this.collection.ElementAt(0).Rating = 0;
            // should fire CollectionChanged Remove in the target view model collection
            this.collection.ElementAt(0).Rating = 5;
        }

        void target_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(e.Action);
        }
    }
}