我正在学习Rx,并试图使用SerialPort从GPS设备实现“NMEA句子阅读器”。它的GPS数据对这个问题的重要性较小,因此让我们澄清一下,NMEA格式由行组成,并且'$'符号代表新条目的开始,因此您将获得类似于以下内容的“句子” :
$[data for first line goes here]
$[data for second line goes here]
...
连接SerialPort的DataReceived事件非常简单:
var port = new SerialPort("COM3", 4800);
var serialPortSource = Observable.FromEventPattern<
SerialDataReceivedEventHandler,
SerialDataReceivedEventArgs>
(
handler => port.DataReceived += handler,
handler => port.DataReceived -= handler
).Select(e => port.ReadExisting());
这给出了
IObservable<string>
,但是很明显,这里返回的字符串不一定要与NMEA句子对齐。例如,对于上面的示例数据,可以得到如下序列:$[data for first line g
oes here]\r\n$[data for
second line goes here]
如何正确地将其变成一系列实际句子?在
IEnumerable
世界中,我可能会从char
序列开始,并编写类似于以下内容的扩展方法:public static IEnumerable<string> ToNmeaSentence(
this IEnumerable<char> characters
)
{
var sb = new StringBuilder();
foreach (var ch in characters)
{
if (ch == '$' && sb.Length > 0)
{
yield return sb.ToString();
sb.Clear();
}
sb.Append(ch);
}
}
现在我想知道在Rx中是否有一种惯用的方法来进行这种操作?
最佳答案
它与Enumerables完全相同。您使用Subscribe
而不是快速枚举,并且使用observer.OnNext
而不是yield return
。哦,您必须使用Observable.Create
,因为C#不像对Enumerables那样对Observers提供语言支持(但是,这并不是Rx的失败)。
Enumerables和Observables是完全一样的东西。一推,另一推。创建它们的语法略有不同。就这样。
public static IObservable<string> ToNmeaSentence(
this IObservable<char> characters
)
{
return Observable.Create<string>(observer => {
var sb = new StringBuilder();
return characters.Subscribe(ch => {
if (ch == '$' && sb.Length > 0)
{
observer.OnNext(sb.ToString());
sb.Clear();
}
sb.Append(ch);
});
});
}
我通常不会在如此低的水平上进行编程,但是Observables不会像Enumerables那样使它复杂化。当人们第一次学习Enumerables时,很难理解。当人们第一次学习可观察性时,很难理解。他们两个都做同样的事情,但是一推又一拉。除了一个区别外,两者之间存在1-1关系。
您认为Rx比Enumerables和LINQ to Objects更复杂是错误的。当您仍在学习时,它就会以这种方式出现。