احصل على العنصر السابق في iobservable دون إعادة تقييم التسلسل

StackOverflow https://stackoverflow.com/questions/2820685

  •  26-09-2019
  •  | 
  •  

سؤال

في IObservable التسلسل (في الامتدادات التفاعلية لـ .NET) ، أود الحصول على قيمة العناصر السابقة والحالية حتى أتمكن من مقارنتها. لقد وجدت مثالًا عبر الإنترنت مشابهًا أدناه والذي ينجز المهمة:

sequence.Zip(sequence.Skip(1), (prev, cur) => new { Previous = prev, Current = cur })

إنه يعمل بشكل جيد إلا أنه يقيم التسلسل مرتين ، والذي أود تجنبه. يمكنك أن ترى أنه يتم تقييمه مرتين مع هذا الرمز:

var debugSequence = sequence.Do(item => Debug.WriteLine("Retrieved an element from sequence"));
debugSequence.Zip(debugSequence.Skip(1), (prev, cur) => new { Previous = prev, Current = cur }).Subscribe();

يُظهر الإخراج ضعف عدد خطوط التصحيح حيث توجد عناصر في التسلسل.

أفهم سبب حدوث ذلك ، لكنني لم أجد حتى الآن بديلاً لا يقيم التسلسل مرتين. كيف يمكنني الجمع بين تقييم تسلسل واحد فقط مع تقييم تسلسل واحد فقط؟

هل كانت مفيدة؟

المحلول

أعتقد أن هناك حلًا أفضل لهذا يستخدم ، والذي يمكن ملاحظته.

public static IObservable<Tuple<TSource, TSource>>
    PairWithPrevious<TSource>(this IObservable<TSource> source)
{
    return source.Scan(
        Tuple.Create(default(TSource), default(TSource)),
        (acc, current) => Tuple.Create(acc.Item2, current));
}

لقد كتبت هذا على مدونتي هنا: http://www.zerobugbuild.com/؟p=213

إضافة

يتيح لك التعديل الإضافي العمل مع أنواع تعسفية أكثر نظافة باستخدام محدد النتائج:

public static IObservable<TResult> CombineWithPrevious<TSource,TResult>(
    this IObservable<TSource> source,
    Func<TSource, TSource, TResult> resultSelector)
{
    return source.Scan(
        Tuple.Create(default(TSource), default(TSource)),
        (previous, current) => Tuple.Create(previous.Item2, current))
        .Select(t => resultSelector(t.Item1, t.Item2));
}

نصائح أخرى

تقييم مرتين هو مؤشر على أ بارد يمكن ملاحظته. يمكنك تحويلها إلى واحدة ساخنة باستخدام .publish ():

var pub = sequence.Publish();
pub.Zip(pub.Skip(1), (...
pub.Connect();

@James World Addendum تبدو رائعة بالنسبة لي ، إن لم يكن Tuple<>, ، وهو ما لا أحبه دائمًا: "كان .eTem1 السابق؟ أم أنها الحالية؟ لا أستطيع أن أتذكر. وما هي الحجة الأولى للمحدد ، هل كان العنصر السابق؟".

لهذا الجزء ، أحببت DCStraw تعريف مخصص ItemWithPrevious<T>. إذن هناك ، تضع الاثنين معًا (آمل أن لا أخلط سابقًا مع التيار) مع بعض التسمية والمرافق:

public static class ObservableExtensions
{
    public static IObservable<SortedPair<TSource>> CombineWithPrevious<TSource>(
        this IObservable<TSource> source, 
        TSource initialValue = default(TSource))
    {
        var seed = SortedPair.Create(initialValue, initialValue);

        return source.Scan(seed,
            (acc, current) => SortedPair.Create(current, acc.Current));
    }

    public static IObservable<TResult> CombineWithPrevious<TSource, TResult>(
        this IObservable<TSource> source,
        Func<SortedPair<TSource>, TResult> resultSelector,
        TSource initialValue = default(TSource))
    {
        var seed = SortedPair.Create(initialValue, initialValue);

        return source
            .Scan(seed,
                (acc, current) => SortedPair.Create(current, acc.Current))
            .Select(p => resultSelector(p));
    }
}

public class SortedPair<T>
{
    public SortedPair(T current, T previous)
    {
        Current = current;
        Previous = previous;
    }

    public SortedPair(T current) : this(current, default(T)) { }

    public SortedPair() : this(default(T), default(T)) { }

    public T Current;
    public T Previous;
}

public class SortedPair
{
    public static SortedPair<T> Create<T>(T current, T previous)
    {
        return new SortedPair<T>(current, previous);
    }

    public static SortedPair<T> Create<T>(T current)
    {
        return new SortedPair<T>(current);
    }

    public static SortedPair<T> Create<T>()
    {
        return new SortedPair<T>();
    }
}

إذا كنت بحاجة فقط إلى الوصول إلى العنصر السابق أثناء الاشتراك ، فربما يكون هذا أبسط شيء سيعمل. (أنا متأكد من أن هناك طريقة أفضل ، وربما مشغل عازلة على iobservable؟ الوثائق متناثرة إلى حد ما في الوقت الحالي ، لذلك لا يمكنني إخبارك حقًا.)

    EventArgs prev = null;

    sequence.Subscribe(curr => 
    {
        if (prev != null)
        {
            // Previous and current element available here
        }

        prev = curr;                              

    });

EventArgs هو مجرد موقف في نوع حجة الحدث الخاص بك.

اتضح أنه يمكنك استخدام متغير للاحتفاظ بالقيمة السابقة وإشارةها وإعادة تعيينها داخل سلسلة IObservable الامتدادات. هذا يعمل حتى داخل طريقة المساعد. مع الكود أدناه يمكنني الاتصال الآن CombineWithPrevious() على بلدي IObservable للحصول على إشارة إلى القيمة السابقة ، دون إعادة تقييم التسلسل.

public class ItemWithPrevious<T>
{
    public T Previous;
    public T Current;
}

public static class MyExtensions
{
    public static IObservable<ItemWithPrevious<T>> CombineWithPrevious<T>(this IObservable<T> source)
    {
        var previous = default(T);

        return source
            .Select(t => new ItemWithPrevious<T> { Previous = previous, Current = t })
            .Do(items => previous = items.Current);
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top