Suppose I have the following class:
public class Person : ReactiveObject, IEditableObject
{
private string name;
private string nameCopy;
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.name = this.nameCopy;
}
public void EndEdit()
{
}
}
Now suppose I want to create an observable sequence (of Unit
) that "ticks" whenever a change is committed to Name
. That is, I only care about changes to Name
that occur between a call to BeginEdit
and and a subsequent call to EndEdit
. Any changes prior to a call to CancelEdit
should be ignored and the sequence should not tick.
I'm struggling to get my head around how I would do this with Rx. It seems I would need state in the pipeline somewhere in order to know whether the change occurred during the window of BeginEdit
/EndEdit
calls. I suppose I could timestamp everything and compare timestamps, but that seems a nasty hack.
I came pretty close using a dedicated Subject
for edit actions along with Observable.Merge
:
public class Person : ReactiveObject, IEditableObject
{
private readonly Subject<EditAction> editActions;
private readonly IObservable<Unit> changedDuringEdit;
private string name;
private string nameCopy;
public Person()
{
this.editActions = new Subject<EditAction>();
var nameChanged = this.ObservableForProperty(x => x.Name).Select(x => x.Value);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
this.changedDuringEdit = nameChanged
.Buffer(editBeginning, _ => editCommitted)
.Where(x => x.Count > 0)
.Select(_ => Unit.Default);
}
public IObservable<Unit> ChangedDuringEdit
{
get { return this.changedDuringEdit; }
}
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.editActions.OnNext(EditAction.Begin);
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.editActions.OnNext(EditAction.Cancel);
this.Name = this.nameCopy;
}
public void EndEdit()
{
this.editActions.OnNext(EditAction.End);
}
private enum EditAction
{
Begin,
Cancel,
End
}
}
However, if several changes are cancelled, and then one is committed, the observable ticks several times on commit (once for each prior cancellation, and once again for the commit). Not to mention the fact that I get a List<Unit>
which I don't actually need. In a way, this would still satisfy my use case, but not my curiosity or sense of code aesthetic.
I feel like Join
should solve this fairly elegantly:
var nameChanged = this.ObservableForProperty(x => x.Name).Select(_ => Unit.Default);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
var editCancelled = this.editActions.Where(x => x == EditAction.Cancel);
var editCancelledOrCommitted = editCancelled.Merge(editCommitted);
this.changedDuringEdit = editBeginning
.Join(nameChanged, _ => editCancelledOrCommitted, _ => editCancelledOrCommitted, (editAction, _) => editAction == EditAction.End)
.Where(x => x)
.Select(_ => Unit.Default);
But this doesn't work either. It seems Join
is not subscribing to editCancelledOrCommitted
, for reasons I don't understand.
Anyone have any ideas how to go about this cleanly?