Как открыть свойства IObservable <T> без использования поля <Темa> <T>

В этот ответ на вопрос о Subject<T> Энигматичность упоминается:

как в сторону, вы должны стараться избегать использования предметов вообще. общее правило заключается в том, что если вы используете тему, то вы делаете что-то не так.

Я часто использую объекты в качестве полей поддержки для свойств IObservable, которые, вероятно, были бы событиями .NET за несколько дней до Rx. например вместо чего-то вроде

public class Thing
{
    public event EventHandler SomethingHappened;

    private void DoSomething()
    {
        Blah();
        SomethingHappened(this, EventArgs.Empty);
    }
}

Я мог бы сделать

public class Thing
{
    private readonly Subject<Unit> somethingHappened = new Subject<Unit>();
    public IObservable<Unit> SomethingHappened
    {
        get { return somethingHappened; }
    }

    private void DoSomething()
    {
        Blah();
        somethingHappened.OnNext(Unit.Default);
    }
}

Итак, если я хочу избежать использования Subject, что было бы правильным способом сделать это? Или я должен придерживаться использования событий .NET в своих интерфейсах, даже если они будут потребляться кодом Rx (возможно, FromEventPattern)?

Кроме того, немного более подробная информация о том, почему использование Subject, как это, является плохой идеей, было бы полезно.

Обновить. Чтобы сделать этот вопрос более конкретным, я говорю об использовании Subject<T> в качестве способа получить код не-Rx (возможно, вы работаете с каким-либо другим наследием кода) в мир Rx. Итак, что-то вроде:

class MyVolumeCallback : LegacyApiForSomeHardware
{
    private readonly Subject<int> volumeChanged = new Subject<int>();

    public IObservable<int> VolumeChanged
    {
        get
        {
            return volumeChanged.AsObservable();
        }
    }

    protected override void UserChangedVolume(int newVolume)
    {
        volumeChanged.OnNext(newVolume);
    }
}

Где вместо использования событий тип LegacyApiForSomeHardware заставляет вас переопределять виртуальные методы как способ получения уведомлений "это только что случилось".

Ответ 1

В ответе на форуме Rx Дэйв Секстон (Rxx) сказал: часть ответ на что-то:

Субъекты представляют собой компоненты с состоянием Rx. Они полезны, когда вам необходимо создать событие, подобное наблюдаемому как поле или локальное переменная.

Что именно происходит с этим вопросом, он также написал подробное сообщение в блоге о "Использовать тему или не использовать тему" , которая завершается

Когда следует использовать тему?

Когда все следующее верно:

  • у вас нет наблюдаемого или ничего, что может быть преобразовано в одно.
  • вам требуется горячее наблюдение.
  • объем вашего наблюдаемого - это тип.
  • вам не нужно определять подобное событие, и подобное событие уже не существует.

Почему я должен использовать объект в этом случае?

Потому что у вас нет выбора!

Итак, отвечая на внутренний вопрос о "подробностях о том, почему использование Субъекта, как будто это плохая идея", - это не плохая идея, это одно из немногих мест, в которых использовалась тема - правильный способ сделать что-то.

Ответ 2

С одной стороны, кто-то может бросить SomethingHappened обратно в ISubject и подавать в него вещи извне. По крайней мере, примените к нему AsObservable, чтобы скрыть предметность лежащего в основе объекта.

Кроме того, тематическое вещание обратных вызовов не является строго чем событие .NET. Например, если один наблюдатель выбрасывает, те, которые следуют в цепочке, не будут вызываться.

    static void D()
    {
        Action<int> a = null;

        a += x =>
        {
            Console.WriteLine("1> " + x);
        };

        a += x =>
        {
            Console.WriteLine("2> " + x);

            if (x == 42)
                throw new Exception();
        };

        a += x =>
        {
            Console.WriteLine("3> " + x);
        };

        a(41);
        try
        {
            a(42);  // 2> throwing will prevent 3> from observing 42
        }
        catch { }
        a(43);
    }

    static void S()
    {
        Subject<int> s = new Subject<int>();

        s.Subscribe(x =>
        {
            Console.WriteLine("1> " + x);
        });

        s.Subscribe(x =>
        {
            Console.WriteLine("2> " + x);

            if (x == 42)
                throw new Exception();
        });

        s.Subscribe(x =>
        {
            Console.WriteLine("3> " + x);
        });

        s.OnNext(41);
        try
        {
            s.OnNext(42);  // 2> throwing will prevent 3> from observing 42
        }
        catch { }
        s.OnNext(43);
    }

В общем случае вызывающий абонент мертв, когда наблюдатель выбрасывает, если вы не защищаете каждый вызов On * (но не производите проглатывание исключений произвольно, как показано выше). Это одинаково для делегатов многоадресной рассылки; исключения будут возвращаться к вам.

В большинстве случаев вы можете достичь того, что хотите делать без субъекта, например. используя Observable.Create, чтобы построить новую последовательность. Такие последовательности не имеют "списка наблюдателей", который возникает из-за множества подписки; у каждого наблюдателя есть своя "сессия" (модель с холодным наблюдением), поэтому исключение из наблюдателя является не чем иным, как самоубийцей в ограниченном пространстве, а не взрывом в середине квадрата.

По сути, субъекты лучше всего использовать на краях графика реактивных запросов (для входных потоков, которые должны быть адресованы другой стороной, которая подает данные, хотя для этого можно использовать обычные .NET-события для этого и связать их с Rx используя методы FromEvent *) и для обмена подписками в графе реактивных запросов (с использованием Publish, Replay и т.д., которые маскируются во время многоадресной рассылки, используя тему). Одна из опасностей использования предметов, которые очень сдержанны из-за их списка наблюдателей и потенциальной записи сообщений, - это использовать их при попытке написать оператор запроса с использованием предметов. 99.999% времени, такие истории имеют печальный конец.

Ответ 3

Один подход для классов, которые имеют простые одноразовые события, заключается в предоставлении метода ToObservable, который создает осмысленный холодный наблюдаемый на основе события. Это более читаемо, чем использование методов Observable factory и позволяет разработчикам, которые не используют Rx, использовать API.

    public IObservable<T> ToObservable()
    {
        return Observable.Create<T>(observer =>
        {
            Action notifier = () =>
            {
                switch (Status)
                {
                    case FutureStatus.Completed:
                        observer.OnNext(Value);
                        observer.OnCompleted();
                        break;

                    case FutureStatus.Cancelled:
                        observer.OnCompleted();
                        break;

                    case FutureStatus.Faulted:
                        observer.OnError(Exception);
                        break;                        
                }
            };

            Resolve += notifier;

            return () => Resolve -= notifier;

        });
    }

Ответ 4

Пока я не могу говорить за Enigmativity напрямую, я предполагаю, что это очень низкоуровневое, что вам действительно не нужно использовать напрямую; все, что предлагает Subject<T> класс, может быть достигнуто с помощью классов в System.Reactive.Linq.

Взяв пример из документации Subject<T>:

Subject<string> mySubject = new Subject<string>();

//*** Create news feed #1 and subscribe mySubject to it ***//
NewsHeadlineFeed NewsFeed1 = new NewsHeadlineFeed("Headline News Feed #1");
NewsFeed1.HeadlineFeed.Subscribe(mySubject);

//*** Create news feed #2 and subscribe mySubject to it ***//
NewsHeadlineFeed NewsFeed2 = new NewsHeadlineFeed("Headline News Feed #2");
NewsFeed2.HeadlineFeed.Subscribe(mySubject);

Это легко достигается с помощью Merge метода расширения на Observable класс:

IObservable<string> feeds = 
    new NewsHeadlineFeed("Headline News Feed #1").HeadlineFeed.Merge(
    new NewsHeadlineFeed("Headline News Feed #2").HeadlineFeed);

Что вы можете затем подписываться нормально. Использование Subject<T> просто делает код более сложным. Если вы собираетесь использовать Subject<T>, тогда вы должны выполнять очень низкоуровневую обработку наблюдаемых данных, где методы расширения не позволяют вам.