如何在不使用Subject <t>支持字段</t> </t>的情况下公开IObservable <t>属性

时间:2012-08-21 11:19:54

标签: c# system.reactive

this answer中提到有关Subject<T>提到的电子事件的问题:

  另外,你应该尽量避免使用主题。该   一般规则是,如果你正在使用一个主题,那么你正在做   出了点问题。

我经常使用subject作为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 个答案:

答案 0 :(得分: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构造一个新序列。这样的序列没有由多个订阅产生的“观察者列表”;每个观察者都有自己的“会话”(冷可观察模型),所以观察者的例外只不过是在一个狭窄的区域内自杀命令,而不是在广场中间吹嘘自己。

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

答案 1 :(得分:11)

answer on the Rx forum,Dave Sexton(Rxx)中,作为答案的一部分说:

  

主题是Rx的有状态组件。它们对于何时有用   你需要创建一个类似事件的observable作为字段或本地   变量

这正是这个问题正在发生的事情,他还撰写了一篇关于To Use Subject Or Not To Use Subject?的深入跟进博客文章,结尾于:

  

我应该何时使用某个主题?

     

如果满足以下所有条件:

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

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

     

因为你别无选择!

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

答案 2 :(得分:2)

虽然我不能直接为Enigmativity发言,但我想这是因为它非常低级,你真的不需要直接使用; Subject<T> class提供的所有内容都可以通过使用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 extension method上的Observable class

可轻松实现这一目标
IObservable<string> feeds = 
    new NewsHeadlineFeed("Headline News Feed #1").HeadlineFeed.Merge(
    new NewsHeadlineFeed("Headline News Feed #2").HeadlineFeed);

然后您可以正常订阅。使用Subject<T>只会使代码更复杂。如果您要使用Subject<T>,那么您应该对扩展方法失败的可观察对象进行一些非常低级的处理。

答案 3 :(得分: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;

        });
    }