ReactiveUI - 具有许多相关属性的模型对象

sam*_*rem 4 c# mvvm reactive-programming system.reactive reactiveui

我有一个WPF MVVM应用程序.我的模型对象是我完全可以控制的POCO.这些对象中的某些属性之间存在关系.

例如:让我说我有

public class Model
{
    public ObservableCollection<double> XCoordinates { get; set; } // Cumulative sum of DistancesBetweenXCoordinates.
    public ObservableCollection<double> DistancesBetweenXCoordinates { get; set; } // {x[1]-x[0], x[2]-x[1], ..., x[n]-x[n-1]}
    public double TotalLength { get; set; } // Sum of DistancesBetweenXCoordinates.
}
Run Code Online (Sandbox Code Playgroud)

我希望最终用户能够编辑距离列表或x坐标列表,其他属性应自动更新.到目前为止,我已经使用INPC事件完成了这项工作,但这很快就变得太乱了.一些UI更新在每个PropertyChange之后完成,所以我想优化它.

由于我的真实应用程序中的某些属性更新对性能影响不小,我不想使用"计算属性",例如

public double TotalLength => DistancesBetweenXCoordinates.Sum();
Run Code Online (Sandbox Code Playgroud)

根据我的阅读,ReactiveUI框架似乎具有我正在寻找的功能.我有一些问题:

1)我目前正在使用另一个框架(Catel),我宁愿完全放弃.我是否可以在.WhenAny()不继承的情况下使用ReactiveUI中的等等ReactiveObject(例如仅通过实现IReactiveObject)?

2)我见过的几乎所有例子都是ReactiveObject在ViewModel中继承的.这是实现我想要的首选方式吗?在我的模型中实现这个没有意义吗?如果我的模型应该只是一个"哑"的POCO而没有任何机制来保持所有相关属性的最新状态,那么这是我的Reactive VM的责任吗?

我会非常感谢一个简单的例子或其他指导.

Ati*_*ita 9

TL;博士

  • 实施INotifyPropertyChanged,你可以使用.WhenAny()
  • ReactiveObject可以用于ViewModel和Model类.它实际上只是一个"反应"对象.即喜欢System.Object.(不相信我?查看来源.)
  • 使用ObservableAsPropertyHelper的计算性能依赖于其它反应性或事件.(见文档)

答案

有几种方法可以解决这个问题,但特别是你想要推理出几件事情:

  1. 如何在不从我正在使用的其他框架迁移的情况下利用反应功能?
  2. 应该ReactiveObject只限于ViewModels?它在普通的Model类中有用吗?

首先,正如您已经注意到的那样,ReactiveUI的大部分内容都包含了强大的功能.WhenAny().你能在框架之间使用这些方法,是的!的.WhenAny(),.WhenAnyValue(),.WhenAnyObservable()方法上工作的任何一个实现目标INotifyPropertyChanged.(相关文件)

实际上,您的现有框架可能已经INotifyPropertyChanged在许多自己的类型上实现,因此.WhenAny()自然地扩展到无缝地处理这些对象.你几乎从来没有真正需要一个ReactiveObject.它只会让你的生活更轻松.

注意:这实际上是ReactiveUI的核心价值之一.在核心,ReactiveUI 实际上只是一堆扩展方法,旨在使现有.Net世界中的observable更容易使用.这使得与现有代码的互操作性成为ReactiveUI最引人注目的功能之一.

现在,应该ReactiveObject用于普通的"哑"模型吗?我想这取决于你想要承担责任的地方.如果你希望你的模型类只包含规范化状态而根本没有逻辑,那么可能不是.但是,如果您的模型旨在处理状态和域相关的逻辑,那么为什么不呢?

注意:关于单一责任主体,软件架构和MVVM,这里有一个更大的哲学争论,但这可能是程序员SE.

在这种情况下,我们关心通知监听器有关某些计算属性的更新,例如TotalLength.(即我们的模型包含一些逻辑)ReactiveObject帮助我们做到这一点?我认同.

在您的场景中,我们希望TotalLengthDistancesBetweenXCoordinates添加或更改元素或其他内容时计算.我们可以结合使用ReactiveObjectObservableAsPropertyHelper.(相关文件)

例如:

class Model : ReactiveObject
{
    // Other Properties Here...

    // ObservableAsPropertyHelper makes it easy to map
    // an observable sequence to a normal property.
    public double TotalLength => totalLength.Value;
    readonly ObservableAsPropertyHelper<double> totalLength;
    public Model()
    {
        // Create an observable that resolves whenever a distance changes or
        // gets added. 
        // You would probably need CreateObservable()
        // to use Observable.FromEventPattern to convert the
        // CollectionChanged event to an observable.
        var distanceChanged = CreateObservable();

        // Every time that a distance is changed:
        totalLength = distanceChanged

            // 1. Recalculate the new length.
            .Select(distances => CalculateTotalLength(distances))

            // 2. Save it to the totalLength property helper.
            // 3. Send a PropertyChanged event for the TotalLength property.
            .ToProperty(this, x => x.TotalLength);
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面,TotalLength每次distanceChangedobservable结算时都会重新计算.例如,这可以在DistanceBetweenXCoordinates发出CollectionChanged事件时发生.此外,因为这只是一个普通的可观察对象,您可以在后台线程上进行计算,从而允许您在长时间操作期间保持UI响应.计算完成后,将PropertyChanged发送事件TotalLength.