在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类型不是使用事件,而是使您覆盖虚拟方法,以此来获取“刚刚发生的”通知。
答案 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;
});
}