我正在学习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更复杂是错误的。当您仍在学习时,它就会以这种方式出现。

10-06 02:59