27

这个回答关于Subject<T>Enigmativity 的问题中提到:

顺便说一句,您应该尽量避免使用主题。一般规则是,如果您使用主题,那么您做错了什么。

我经常使用主题作为IObservable属性的支持字段,这在 Rx 之前可能是 .NET 事件。例如,而不是类似的东西

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 类型不是使用事件,而是让您覆盖虚拟方法,作为获取“刚刚发生”通知的一种方式。

4

4 回答 4

17

一方面,有人可以将 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 来构造一个新序列。这样的序列没有由多个订阅产生的“观察者列表”;每个观察者都有自己的“会话”(cold observable 模型),因此观察者的例外无非是在有限区域内发出自杀命令,而不是在广场中间炸毁自己。

本质上,主题最好用在反应式查询图的边缘(对于需要由提供数据的另一方寻址的入口流,尽管您可以为此使用常规 .NET 事件并使用 FromEvent* 将它们桥接到 Rx方法)以及用于在反应式查询图中共享订阅(使用发布、重播等,它们是变相的多播调用,使用主题)。使用主题的危险之一 - 由于它们的观察者列表和潜在的消息记录而非常有状态 - 是在尝试使用主题编写查询运算符时使用它们。99.999% 的情况下,这样的故事都有一个悲伤的结局。

于 2012-08-21T18:52:34.790 回答
12

Rx 论坛上的回答中,Dave Sexton(Rxx的)在回答某事时说:

主题是 Rx 的有状态组件。当您需要创建类似事件的可观察对象作为字段或局部变量时,它们很有用。

这个问题到底是怎么回事,他还写了一篇关于使用主题还是不使用主题的深入跟进博客文章?最后是:

我什么时候应该使用主题?

当以下所有条件都为真时:

  • 您没有可观察的或任何可以转换为可观察的东西。
  • 你需要一个 hot observable。
  • 你的 observable 的范围是一种类型。
  • 您不需要定义类似的事件,并且不存在类似的事件。

在这种情况下我为什么要使用主题?

因为你别无选择!

因此,回答“为什么像这样使用 Subject 是一个坏主意的详细信息”的内在问题 - 这不是一个坏主意,这是少数几个使用 Subject 的地方之一是正确的做事方式。

于 2012-11-27T16:11:07.760 回答
2

虽然我不能直接说 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>那么您应该对扩展方法失败的可观察对象进行一些非常低级的处理。

于 2012-08-21T14:52:08.570 回答
2

对于具有简单一次性事件的类,一种ToObservable方法是提供一种基于事件创建有意义的冷可观察对象的方法。这比使用 Observable 工厂方法更具可读性,并且允许不使用 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;

        });
    }
于 2012-08-22T08:10:57.200 回答