有条件地结合可观察量

时间:2015-08-10 09:14:23

标签: c# system.reactive

我有两个可观察对象,一个是IObservable<AlertData>,另一个是IObservable<SoundRequestData>AlertData包含一个属性Id,该属性知道哪个SoundRequestData属于它。 SoundRequestData只知道自己,并且Id属性可以与AlertData中的属性匹配。

我想将这两种数据类型合并为一种新类型AlertDataViewModel。但是,我无法确定两个可观察数据中的数据顺序是否相同。我现在不关心输出中的顺序。

我想要的是将AlertDataSoundRequestData匹配。

我现在这样做的方式,虽然有效但很慢,但要等到其中一个可观测量完成后将所有数据提取到ObservableCollection。然后我启动另一个可观察对象并匹配Id。

有更好的方法吗?我想这可以表示为以下大理石图:

Imgur

所以a.id=1最多匹配3.id=1b.id=2最多匹配4.id=2,依此类推。

2 个答案:

答案 0 :(得分:2)

首先让我们为IObserver<T>引入一些扩展方法。

public static IObserver<T> Safe<T>(this IObserver<T> observer)
{
    var done = false;
    return Observer.Create<TResult>(
        value =>
        {
            if (!done)
            {
                observer.OnNext(value);
            }
        },
        error =>
        {
            if (!done)
            {
                done = true;
                observer.OnError(error);
            }
        },
        () =>
        {
            if (!done)
            {
                done = true;
                observer.OnCompleted();
            }
        });
}

这只是确保在模式OnNext*(OnError|OnCompleted)中调用观察者,并且忽略对它的违反。

我们现在可以通过按键缓冲两个序列中的值来实现你所描述的运算符,只有在两个序列之间存在密钥匹配时才发出它们。

public static IObservable<TResult> Join<T1, T2, TKey, TResult>(
    IObservable<T1> source1,
    IObservable<T2> source2,
    Func<T1, TKey> key1,
    Func<T2, TKey> key2,
    Func<T1, T2, TResult> selector)
{
    return Observable.Create<TResult>(observer =>
    {
        var dict1 = new Dictionary<TKey, T1>();
        var dict2 = new Dictionary<TKey, T2>();
        var gate = new object();
        var safeObserver = observer.Safe();
        Action<TKey> emit = k =>
        {
            T1 value1;
            T2 value2;
            if (dict1.TryGetValue(k, out value1) && dict2.TryGetValue(k, out value2))
            {
                var result = selector(value1, value2);
                safeObserver.OnValue(result);
                dict1.Remove(k);
                dict2.Remove(k);
            }
        };
        return new CompositeDisposable(
            source1.Synchronize(gate).Subscribe(
                value1 =>
                {
                    var k = key1(value1);
                    dict1[k] = value1;
                    emit(k);
                },
                safeObserver.OnError,
                safeObserver.OnCompleted),
            source2.Synchronize(gate).Subscribe(
                value2 =>
                {
                    var k = key2(value2);
                    dict2[k] = value2;
                    emit(k);
                },
                safeObserver.OnError,
                safeObserver.OnCompleted));
    });
}

示例:

IObservable<AlertData> alertDatas = ...;
IObservable<SoundRequestData> = soundRequestDatas = ...;
IObservable<AlertDataViewModel> alertDataViewModels = Join(
    alertDatas,
    soundRequestDatas,
    alertData => alertData.Id,
    soundRequestData => soundRequestData.Id,
    (alertData, soundRequestData) => new AlertDataViewModel
    {
        AlertData = alertData,
        SoundRequestData = soundRequestData
    });

答案 1 :(得分:1)

这不是最漂亮的,但它会起作用。

它将返回此类,它只是原始的两个集合:

class Aggregate
{
    public AlertData AlertData {get;set;}
    public SoundRequestData SoundRequestData { get; set; }
    public int Id { get { return AlertData == null ? SoundRequestData.Id : AlertData.Id; } } 
}

这是加入逻辑:

var joined = Observable.Merge(   // Convert the two sources into half-filled aggregates and merge them
        source1.Select(a => new Aggregate() { AlertData = a }),
        source2.Select(s => new Aggregate() { SoundRequestData = s }))
    .GroupBy(a => a.Id)
    // We only need two for each Id
    .Select(group => group.Take(2))  
    // This looks ugly, but is just joining the two messages into one
    .Select(group => group.Aggregate(new Aggregate(), (agg, newData) => new Aggregate() { AlertData = agg.AlertData ?? newData.AlertData, SoundRequestData = agg.SoundRequestData ?? newData.SoundRequestData }))
    // Back to one stream
    .Merge();