如果数组元素发生变化,则可观察到

Posted

技术标签:

【中文标题】如果数组元素发生变化,则可观察到【英文标题】:Observable if the array element is changed 【发布时间】:2022-01-20 09:06:35 【问题描述】:

如何用一行代码写出这段代码?

public class RadGridColumnObservable

    public RadGridColumnObservable(RadGridView grid, IMessageBus messageBus)
    
        this.grid = grid;
        this.messageBus = messageBus;
    

    public IDisposable Initialize()
    
        CreateStates();

        return Observable
            .Interval(TimeSpan.FromSeconds(1))
            .Subscribe(_ => CheckUp());
    

    //

    private readonly RadGridView grid;
    private readonly IMessageBus messageBus;
    private readonly List<bool> states = new List<bool>();

    //

    private void CreateStates()
    
        states.Clear();
        states.AddRange(grid.Columns.Select(it => it.IsVisible));
    

    private void CheckUp()
    
        if (states.SequenceEqual(grid.Columns.Select(it => it.IsVisible))) return;

        CreateStates();
        messageBus.Publish(new NotifyMessage(MessageTypes.HistoryColumnsChanged));
    

想法是:我想检查IsVisible属性是否改变了。

我不喜欢用这条线:

private readonly List<bool> states = new List<bool>();

【问题讨论】:

"isVisible" 改变了什么值?你需要一些结构来保持值比较 当我隐藏/显示列时,我没有为 RadGridView 找到任何事件,我使用线程将旧列与新列进行比较,以查看“IsVisible”何时更改。代码工作正常,但我想用 Observable 优化它。 您正在使用的网格是 UI 网格吗?如果是这样,您需要发送ObserveOn 以防止出现线程问题。 【参考方案1】:

您可以通过以下方式获取您的 IsVisible 值:

private IObservable<List<bool>> CreateStatesObservable()

    return Observable.Interval(TimeSpan.FromSeconds(1))
                        .Select(_ => grid.Columns.Select(it => it.IsVisible));

然后使用Scan 来跟踪你之前的值:

public void Initialize()

    var observable = 
            CreateStatesObservable()
                        .Scan(
                            (prev: default(List<bool>), actual: default(List<bool>)),
                            (acc, c) => (acc.actual, current))
                        .Where(values => !values.actual.SequenceEqual(values.prev))
                        .Select(_ => true);

或者您可以使用Defer 并执行以下操作:

public void Initialize2()

    var observable = CreateStatesObservable();

    var deferred =
        Observable.Defer(
                      () =>
                      
                          List<bool> previous = null;

                          return observable.Select(
                              values =>
                              
                                  if (previous is null)
                                  
                                      previous = values;
                                      return false;
                                  

                                  if (!values.SequenceEqual(previous))
                                  
                                      previous = values;
                                      return true;
                                  

                                  return false;
                              );
                      )
                  .Where(value => value);

这两个选项都应该给你一个 observable,它只在 IsVisible 列之一发生变化时产生一个值。 (该值只是 true

编辑:

您也可以将DistinctUntilChanged() 与您自己的IEqualityComparer 一起使用,如下所示:

class ListComparer : IEqualityComparer<List<bool>>

    bool IEqualityComparer<List<bool>>.Equals(List<bool>? a, List<bool>? b)
    
        if (a is null && b is null)
        
            return true;
        
        
        if (a is null || b is null)
        
            return false;
        
        
        return a.SequenceEqual(b);
    

    int IEqualityComparer<List<bool>>.GetHashCode(List<bool> obj)
    
        return obj.GetHashCode();
    

然后像这样使用它:

public void Initialize()

    var observable = 
            CreateStatesObservable()
                        .DistinctUntilChanged(new ListComparer())
                        .Select(_ => true);

【讨论】:

非常好的答案。非常感谢。 乐于助人:)。现在我再想一想,您也许还可以使用 DistinctUntilChanged() 并编写自己的平等比较器来实现 IEqualityComparer&lt;List&lt;bool&gt;&gt; 我用DistinctUntilChanged()的例子更新了我的答案 +1 我使用 scan 的方法,我测试过 Observable.Defer 并且工作良好。但我认为扫描方法更好。我是 system.reactive 的初学者,但我真的很喜欢这个想法。【参考方案2】:

我认为您可以将您的Initialize() 调整为这样的:

public IDisposable Initialize()

    return Observable
        .Interval(TimeSpan.FromSeconds(1))
        .Select(v => grid.Columns.Select(it => it.IsVisible).ToList())
        .Scan((prev: new List<bool>(), actual: new List<bool>()),
            (acc, c) => (acc.actual, c))
        .Where(it => !it.actual.SequenceEqual(it.prev))
        .Subscribe(_ => messageBus.Publish(new NotifyMessage(MessageTypes.HistoryColumnsChanged)));

这应该给你同样的结果,你只需要一次SequenceEqual()。扫描就在那里,所以你有“当前”和“以前”的值。在你的情况下,你真的不需要在里面做检查。

【讨论】:

【参考方案3】:

我现在使用这样的东西:

public class RadGridColumnObservable

    public RadGridColumnObservable(RadGridView grid, IMessageBus messageBus)
    
        this.grid = grid;
        this.messageBus = messageBus;
    

    public IDisposable Initialize()
    
        return Observable
            .Interval(TimeSpan.FromSeconds(1))
            .Scan((prev: grid.Columns.Select(it => it.IsVisible).ToList(), actual: grid.Columns.Select(it => it.IsVisible).ToList()),
                (acc, c) =>
                
                    if (!acc.prev.SequenceEqual(acc.actual))
                        acc.prev = acc.actual;

                    acc.actual = grid.Columns.Select(it => it.IsVisible).ToList();
                    return acc;
                )
            .Where(it => !it.prev.SequenceEqual(it.actual))
            .Subscribe(it => messageBus.Publish(new NotifyMessage(MessageTypes.HistoryColumnsChanged)));
    

    //

    private readonly RadGridView grid;
    private readonly IMessageBus messageBus;

但我不喜欢,因为我第二次使用“SequenceEqual”。

【讨论】:

以上是关于如果数组元素发生变化,则可观察到的主要内容,如果未能解决你的问题,请参考以下文章

在 RxCocoa/RxSwift 中,如何观察 BehaviorRelay<[object]> 数组大小发生变化

将更改发布到可观察对象的集合

Vuejs 观察动态数组元素的变化

观察一个元素有多少个孩子?

具有非重复元素和变化范围的随机发生器

Vue - 如果添加或修改了对象,则深度观察对象数组的变化