聆听列表中项目内的可观察事件

时间:2017-10-18 00:49:49

标签: c# system.reactive

我有IObservable<ImmutableArray<T>>,其中每个T都有相应的删除命令IObservable<Unit>。当用户在列表中的项目上单击“删除”时,我尝试响应。当一个项目添加到列表中时,我想开始监听(订阅)删除命令。当一个项目从列表中删除时,我想停止收听(取消订阅)。如果项目X被添加到列表中并且列表更改了很多次,我想确保我只在X上订阅了删除命令 - 当它被添加时 - 而不是每次列表更改时取消订阅并重新订阅

最初我尝试使用Switch命令执行此操作。但后来我意识到,每次列表更改时,它可能都取消订阅并重新订阅每个项目。这使得难以使用Pairwise或Scan,因为每次列表更改时,我的订阅都会被删除,而我会从一个新的订阅开始。我认为我遇到了其他问题,但它通常看起来并不是所有订阅和取消订阅的正确答案。

所以我认为答案涉及使用TakeUntil。我将监控列表,对其进行成对,并且能够始终知道什么是新的和已删除的。然后,当我订阅每个项目时,我将TakeUntil该项目位于已移除的集合中。那就是这个想法,但我遇到了代码问题。

我正在与之合作:

interface IListItem {
    IObservable<Unit> Delete { get; }
    string ListItemName { get; }
}

IObservable<ImmutableList<IListItem>> _list;

_list...something here...Subscribe(i=>{
    Console.WriteLine($"You requested to delete {i}!");
});

2 个答案:

答案 0 :(得分:1)

嗯,我觉得我在这里得到了一个不错的答案。我想要的时间稍长,但我认为它有效。如果有人想建议更短或更简单的东西,我很乐意听到它。部分.Select(add => add.Delete.Select(_ => add).TakeUntil...很奇怪。基本上,当为特定列表项调用delete命令时,我想返回调用Delete命令的列表项。

_items
.Pairwise((before, after) => new
{
    AddedToList = after.Except(before),
    RemovedFromList = before.Except(after)
})
.Publish(p =>
{
    var additions = p.SelectMany(i => i.AddedToList);
    var removals = p.SelectMany(i => i.RemovedFromList);
    return
        additions
        .Select(add =>
            add
            .Delete
            .Select(_ => add)
            .TakeUntil(removals.Where(rem => rem == add)))
        .Merge();
}).Subscribe(i =>
{
    // process the delete request on i
    // at the end, submit the modified array to _items
});

答案 1 :(得分:0)

这是我最终建立的。它对我来说非常好用。第一部分是通用课程,用于计算和报告两组之间的差异。

public class SetComparison<T>
{
    private Lazy<IImmutableSet<T>> _added;
    private Lazy<IImmutableSet<T>> _removed;
    private Lazy<IImmutableSet<T>> _intersection;

    public SetComparison(IEnumerable<T> previous, IEnumerable<T> current)
    {
        if (previous == null) throw new ArgumentNullException(nameof(previous));
        if (current == null) throw new ArgumentNullException(nameof(current));
        Previous = previous.ToImmutableHashSet();
        Current = current.ToImmutableHashSet();
        _added = new Lazy<IImmutableSet<T>>(() => Current.Except(Previous));
        _removed = new Lazy<IImmutableSet<T>>(() => Previous.Except(Current));
        _intersection = new Lazy<IImmutableSet<T>>(() => Current.Intersect(Previous));
    }

    public IImmutableSet<T> Previous { get; }
    public IImmutableSet<T> Current { get; }
    public IImmutableSet<T> Added => _added.Value;
    public IImmutableSet<T> Removed => _removed.Value;
    public IImmutableSet<T> Intersection => _intersection.Value;
}

这是一个像F#Pairwise这样的通用运算符,用于获取序列并将其转换为一系列重叠的项目。

public static IObservable<TResult> Pairwise<TSource, TResult>(
    this IObservable<TSource> source,
    Func<TSource, TSource, TResult> resultSelector) =>
    source.Scan(
        (default(TSource), default(TSource)),
        (pair, current) => (pair.Item2, current))
        .Skip(1)
        .Select(p => resultSelector(p.Item1, p.Item2));

这现在成对了两套。

public static IObservable<SetComparison<T>> PairwiseSetComparison<T, TCollection>(this IObservable<TCollection> source) where TCollection : IEnumerable<T> =>
    source
    .Pairwise((a, b) => new SetComparison<T>(a, b));


public static IObservable<SetComparison<T>> PairwiseSetComparison<T>(this IObservable<ImmutableArray<T>> source) =>
    source.Pairwise((a, b) => new SetComparison<T>(a, b));

最后,到

public static IObservable<TResult> Merge<TResult, T>(this IObservable<SetComparison<T>> source, Func<T, IObservable<TResult>> result) =>
    source
    .Publish(s =>
    {
        var additions = s.SelectMany(i => i.Added);
        var removals = s.SelectMany(i => i.Removed);
        return
            additions
            .Select(add => result(add).TakeUntil(removals.Where(rem => rem.Equals(add))))
            .Merge();
    });

所以现在当我想订阅仅在当前集合中的observable时,我会做这样的事情......

_items
.PairwiseSetComparison()
.Merge(i => i.Delete.Select(_ => i))
.Subscribe(i=> /* user clicked Delete on item i */)

完成所有这些操作的另一种方法是,当用户调用它时,列表中每个项目上的Delete ICommand实际上会运行一些委托。这样我就不必在调用命令时实际订阅了。不过,我更喜欢让我的清单中的物品完全不知道它们周围的环境。