Observable if the array element is changed

Viewed 88

How I can write this code with a single line?

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));
    }
}

The idea is: I want to check if the IsVisible property is changed.

I don't like to use this line:

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

You could get your IsVisible values with something like this:

private IObservable<List<bool>> CreateStatesObservable()
{
    return Observable.Interval(TimeSpan.FromSeconds(1))
                        .Select(_ => grid.Columns.Select(it => it.IsVisible));
}

And then use Scan to keep track of your previous value:

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);
}

Or you could use Defer and do something like this:

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);
}

Both options should give you an observable which only produces a value when one of the columns IsVisible has changed. (That value just being a true)

Edit:

You can also use DistinctUntilChanged() with your own IEqualityComparer like this:

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();
    }
}

And then use it like this:

public void Initialize()
{
    var observable = 
            CreateStatesObservable()
                        .DistinctUntilChanged(new ListComparer())
                        .Select(_ => true);
}

I use now something like this:

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;
}

but i don't like because i use 2nd time "SequenceEqual".

I think you can adjust your Initialize() to something like this:

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)));
}

This should give you the same result and you only need SequenceEqual() once. The scan is just there so you have the "current" and the "previous" value. You don't really need to do a check inside it in your case.

Related