问题
Let's say I have something like this:
var observable = observable1
.Merge(observable2)
.Merge(observable3);
var subscription = observable.Subscribe(ValueHandler);
...
public void ValueHandler(string nextValue)
{
Console.WriteLine($"Next value {nextValue} produced by /* sourceObservable */");
}
Short of adding that reference along with the value inside of each observable implementation, is there a way to get the source observable out of observable1
, observable2
and observable3
that produced the next value?
回答1:
No, that's exactly the opposite of what Merge
is designed to do. Merge
is designed to take multiple streams and treat them like one. If you wanted some way to treat them separately, use a different operator.
EDIT
As for an operator which passes on the source, the short answer is no. Rx is about reacting to messages, the source is irrelevant. I'm not even sure if you could define conceptually what a 'source' is in Rx:
var observable1 = Observable.Interval(TimeSpan.FromSeconds(1)).Take(5);
var observable2 = observable1.Select(a => a.ToString());
var subscription = observable2.Subscribe(s => s.Dump());
Is the subscription source observable1
, observable2
, or some sort of pointer to the system clock?
If you wanted to separate the messages entering into a merge then you could use Select
as follows:
var observable = observable1.Select(o => Tuple.Create("observable1", o))
.Merge(observable2.Select(o => Tuple.Create("observable2", o)))
.Merge(observable3.Select(o => Tuple.Create("observable3", o)));
If that's too messy, then you could easy make an extension method to clean it up.
I'll also add that the code you posted in your answer isn't very Rx-like. General guidelines are to avoid directly implementing IObservable
. A School
can more concisely be re-written as follows:
public class School
{
//private Subject<Student> _subject = null;
private readonly ISubject<Student> _applicationStream = null;
public static readonly int MaximumNumberOfSeats = 100;
public string Name { get; set; }
public School(string name)
: this(name, new Subject<Student>())
{
}
public School(string name, ISubject<Student> applicationStream )
{
Name = name;
_applicationStream = applicationStream;
}
public void AdmitStudent(Student s)
{
_applicationStream.OnNext(s);
}
public IObservable<Student> ApplicationStream()
{
return _applicationStream;
}
public IObservable<Student> AcceptedStream()
{
return _applicationStream
.SelectMany(s => s != null ? Observable.Return(s) : Observable.Throw<Student>(new ArgumentNullException("student")))
.Distinct()
.Take(MaximumNumberOfSeats);
}
}
This way you can subscribe to all applications, the acceptances, and if you wanted to, the rejects, etc.. You also have less state (no List<Student>
), and ideally you would even remove the Subject<Student> applicationStream
and turn that into an Observable that gets passed in somewhere.
回答2:
No, there is nothing out-of-the-box that will supply us with this information about which observable generated the value.
That is because that is an implementation detail.
If the designers of Rx had to provide this additional information, they could only do it by imposing some kind of a restriction on the TSource
generic type parameter. And that would not be nice. That is the only way a value handler could get to know who generated the value.
So, the onus of getting this information is upon the implementation of the developer using Rx.
To make an example out of it, let us say you had a School
class, which was an observable of students like so:
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
namespace SchoolManagementSystem
{
public class Student
{
public Student(string name)
{
Name = name;
}
public string Name { get; set; }
}
public class School : IObservable<Student>
{
private List<Student> _students;
private Subject<Student> _subject = null;
public static readonly int MaximumNumberOfSeats = 100;
public string Name { get; set; }
public School(string name)
{
Name = name;
_students = new List<Student>();
_subject = new Subject<Student>();
}
public void AdmitStudent(Student student)
{
if (student == null)
{
var ex = new ArgumentNullException("student");
_subject.OnError(ex);
throw ex;
}
try
{
if (_students.Count == MaximumNumberOfSeats)
{
_subject.OnCompleted();
return;
}
if (!_students.Contains(student))
{
_students.Add(student);
_subject.OnNext(student);
}
}
catch(Exception ex)
{
_subject.OnError(ex);
}
}
public IDisposable Subscribe(IObserver<Student> observer)
{
return _subject.Subscribe(observer);
}
}
}
And the client code like so:
using SchoolManagementSystem;
using System;
using System.Reactive.Linq;
namespace Client
{
class Program
{
static void Main(string[] args)
{
var school1 = new School("School 1");
var school2 = new School("School 2");
var school3 = new School("School 3");
var observable = school1
.Merge(school2)
.Merge(school3);
var subscription = observable
.Subscribe(PrintStudentAdmittedMessage, PrintNoMoreStudentsCanBeAdmittedMessage);
school1.FillWithStudents(100);
school2.FillWithStudents(102);
school3.FillWithStudents(101);
Console.WriteLine("Press any key to stop observing and to exit the program.");
Console.ReadKey();
subscription.Dispose();
}
static void PrintStudentAdmittedMessage(Student student)
{
Console.WriteLine($"Student admitted: {student}");
}
static void PrintNoMoreStudentsCanBeAdmittedMessage()
{
Console.WriteLine("No more students can be admitted.");
}
}
}
Then, for the client to know which school a student was admitted to, you must change the type of TSource
of the IObservable<TSource>
. In this case, you must change the fact that a School
is an IObservable<Student>
.
However, changing that goes against the semantics of the domain model. Therefore, the way to get around that would be to include a reference to the School
inside of the student like so:
using System;
using System.Collections.Generic;
namespace SchoolManagementSystem
{
public class Student
{
public Student(string name)
{
Name = name;
}
// Add this new property so you get this information
// in the value handler
public School School { get; set; }
public string Name { get; set; }
public override string ToString()
{
return string.Format($"({School.Name}: {Name})");
}
}
}
And then you would change the AdmitStudent
method of the School
class to indicate which school the student is being admitted to, like so:
public void AdmitStudent(Student student)
{
try
{
if (_students.Count == MaximumNumberOfSeats)
{
...
}
if (!_students.Contains(student))
{
// Add this line to indicate which school
// the student is being admitted to
student.School = this;
_students.Add(student);
_subject.OnNext(student);
}
}
catch(Exception ex)
{
_subject.OnError(ex);
}
}
来源:https://stackoverflow.com/questions/38479541/how-to-know-which-observable-the-next-value-comes-from