问题
I'm new to using Rx and I've been trying to re-write my MVC w/ Service Layer (NOT ASP!) to use this awesome new-fangled Rx. I have a class called Remote
which encapsulates a NetworkStream
. The Remote
uses Rx to listen to bytes from the NetworkStream
and once it works out it's received a full message worth of data, it decodes that data into an IMessage
.
I get how I can read from the Stream
continuously using Rx from inside the Remote
, but how do I publish decoded IMessage
from that stream to the outside world from the Remote
? Am I supposed to use the classic evented style in C# and have the consumers of the events use Observable.FromEvent
?
I only ask because I've read around that IObservable
is not meant to be implemented anymore.
回答1:
Am I supposed to use the classic evented style in C# and have the consumers of the events use
Observable.FromEvent
?
If you're not positively forced to do so, do not make an API using C# style events. The IObservable<T>
is a powerful, generic, widely supported interface which allows us to treat events as first class citizens while easily managing subscriptions. Even if your consumer isn't using Rx, they'll be able to understand and use IObservable<T>
more easily than using C# events. What they do with those events is up to them, but the IObservable<T>
abstraction is clearer and simpler.
I've read around that IObservable is not meant to be implemented anymore.
It reality, what we mean is that there's probably not reason to implement IObservable<T>
on your own, because we have tools to create instances of that type for us.
We have Observable.Create(...)
which allows us to create observables from scratch. We have different types of Subjects like Subject<T>
, BehaviorSubject<T>
, ReplaySubject<T>
, etc, which can be used as proxies and allow us to multicast values to multiple consumers, and we have operators which allow us to transform/compose any IObservable<T>
into another type or kind of IObservable<T>
.
but how do I publish decoded IMessage from that stream to the outside world from the
Remote
?
You expose an IObservable<T>
on your class / interface.
public interface IRemote
{
public IObservable<IMessage> Messages { get; }
}
You could implement this in any number of ways. First, you could make it so each subscription to Messages
gets it's own subscription to your underlying logic...
public class Remote : IRemote
{
private IObservable<IMessage> _messages = ...;
public IObservable<IMessage> Message {
get {
return message;
}
}
}
Or you could make sure that there's only ever one subscription to the underlying logic...
public class Remote : IRemote
{
private IObservable<IMessage> _messages = ...;
private IObservable<IMessage> _refCountedMessages
= this._messages
.Publish()
.RefCount();
public IObservable<IMessage> Message {
get {
return this._refCountedMessages;
}
}
}
Or you could make the connection process extremely explicit in nature.
public interface IRemote
{
public IObservable<IMessage> Messages { get; }
public IDisposable Connect();
}
public class Remote : IRemote
{
private IObservable<IMessage> _messages = ...;
private IObservable<IMessage> _connectableMessages
= this._messages
.Publish();
public IObservable<IMessage> Message {
get {
return this._connectableMessages;
}
}
public IDisposable Connect()
{
return this._connectableMessages.Connect();
}
}
回答2:
I assume you problem is similar to this question How to "reconstruct lines" of data read from SerialPort using Rx?
Instead of getting strings pushed at you that you then change into messages, you will get bytes. No problem, you can use the same WindowBy concept to slice up your sequence of bytes into windows that can then be translated/converted/mapped/whatever into your IMessage
.
Building on Christopher Harris' answer. Here is an implementation of his proposed interfaces. The point here is to show that you can expose observable sequences that are just queries built on top of an underlying observable sequence. In this case the Messages sequence is just a query over the network sequence. With layering we get the level of abstraction that the consumer wants.
void Main()
{
var networkStream = new NetworkStream();
var remote = new Remote(networkStream);
remote.GetMessages().Dump("remote.GetMessages()");
}
// Define other methods and classes here
public class NetworkStream
{
//Fake getting bytes off the wire or disk
public IObservable<byte> GetNetworkStream()
{
var text = @"Line 1.
Hello line 2.
3rd and final line!";
return Observable.Zip(
UTF8Encoding.UTF8.GetBytes(text).ToObservable(),
Observable.Interval(TimeSpan.FromMilliseconds(100)),
(character, time)=>character);
}
}
public interface IMessage
{
string Content {get;}
}
public class Message : IMessage
{
public Message(string content)
{
Content = content;
}
public string Content {get; private set;}
}
public interface IRemote
{
IObservable<IMessage> GetMessages();
}
public class Remote : IRemote
{
private readonly NetworkStream _networkStream;
private readonly byte[] _delimiter = UTF8Encoding.UTF8.GetBytes(Environment.NewLine);
public Remote(NetworkStream networkStream)
{
_networkStream = networkStream;
}
public IObservable<IMessage> GetMessages()
{
return _networkStream.GetNetworkStream()
.WindowByExclusive(b => _delimiter.Contains(b))
.SelectMany(window=>window.ToArray().Select(bytes=>UTF8Encoding.UTF8.GetString(bytes)))
.Select(content=>new Message(content));
}
//TODO Add IDispose and clean up your NetworkStream
}
public static class ObservableEx
{
public static IObservable<IObservable<T>> WindowByExclusive<T>(this IObservable<T> input, Func<T, bool> isWindowBoundary)
{
return Observable.Create<IObservable<T>>(o=>
{
var source = input.Publish().RefCount();
var left = source.Where(isWindowBoundary).Select(_=>Unit.Default).StartWith(Unit.Default);
return left.GroupJoin(
source.Where(c=>!isWindowBoundary(c)),
x=>source.Where(isWindowBoundary),
x=>Observable.Empty<Unit>(),
(_,window)=>window)
.Subscribe(o);
});
}
}
来源:https://stackoverflow.com/questions/23788130/publishing-decoded-messages-from-a-stream-while-keeping-encapsulation-in-rx