我目前正在为一个应用程序构建一个向导系统,我们正在使用 ReactiveUI,因此是 Rx。
向导中的每一步都实现IWizardStep<T>了T向导最终生成的数据类型。
每个步骤都具有显示用户可以移动到下一个步骤的能力,以便基于数据输入启用分支。
可以认为该步骤具有与以下类似的结构:
public interface IWizardStep<T>
{
IObservable<IStepChoice<T>> NextStepChoice {get;}
}
只需IStepChoice<T>:
public interface IStepChoice<T>
{
IWizardStep<T> Step {get;}
string Reason {get;}
}
为了计算从开始到结束的当前路径,以显示给用户,我需要能够从开始步骤开始,并NextStepChoice递归地沿着链走,直到它达到空值(这是NextStepChoice可观察的有效行为发出 null 以指示向导结束)。
我已经看过了,Observable.Scan但我一生都无法弄清楚如何以递归方式使其正常工作。
我还看了看Observable.Generate哪个看起来很有希望,因为这是一个经典的展开式问题;唯一的问题是Generate需要一个函数来确定何时中断循环,但我需要评估内部 observable 来解决这个问题。
Observable.Generate(
new WizardStepChoice<T>(start, null),
choice => choice != null,
choice => choice.ChosenStep.NextStepChoice,
choice => choice);
这将是理想的,并产生我想要的输出,但NextStepChoice那里的选择器显然无法编译,因为它是 anIObservable<IWizardStepChoice<T>>而不是IWizardStepChoice<T>.
我看过使用Aggregate,Scan但是由于这些是更多折叠驱动的操作,而且我只有起始元素,这是我正在寻找的展开 ala Generate,但我需要它能够评估嵌套的 observable。
Observable.Create也许是我可以利用的东西?我已经尝试过了,并想出了:
Path = CurrentStep.Select(_ => Observable.Create<IWizardStep<T>>(async observer =>
{
IWizardStepChoice<T> next = new WizardStepChoice<T>(start, null);
observer.OnNext(next.ChosenStep);
while (next != null)
{
next = await next.ChosenStep.NextStepChoice;
observer.OnNext(next.ChosenStep);
}
observer.OnCompleted();
return Disposable.Empty;
}).Aggregate(new List<IWizardStep<T>>(),
(l, s) =>
{
l.Add(s);
return l;
})).Switch().Publish().RefCount();
哪个有我想要的所有正确签名IWizardStep<T>->IReadOnlyList<IWizardStep<T>>,所以乍一看它看起来不错,但它不起作用;它触发了,我可以通过,但是一旦它到达等待它就会挂起并且不会回来。
我感觉我很接近了,这是一个日程安排问题,所以我的问题是:
- 解决这个问题的最佳方法是什么,我接近了吗?
- 如果这是正确的,为什么等待会出现问题,我该如何解决?
更新
经过一些修补后,我注意到 await 可能挂起,因为该 observable 尚未(并且不会)发出一个值(duh),我现在通过初始化每个步骤的值来解决这个问题向导的开始。
我什至通过添加一个属性来检查这一点IWizardStep<T>-IWizardStepChoice<T> LatestStepChoice {get;}这只是与:
NextStepChoice.Subscribe(c => _latestStepChoice = c);
这是在 step 类本身上完成的,我可以确认它工作得很好。
然而等待仍然挂起,所以我尝试了:
- 让它
Replay(1)等待调用.Subscribe()将获得价值 - 这不起作用 Repeat()即使订阅了某些东西,它也会看到新的价值 - 这只是让整个事情挂起。
显然我在这里遗漏了一些东西,我想要它,以便当 await 查询 observable 时,它将被赋予看到的最新值,这是我认为Replay(1)会实现的;我也尝试过,但由于这种行为PublishLast(),未来的更新不会得到尊重。AsyncSubject<T>
现在我已经切换到使用 self-subscribed 属性,但这并不理想,如果我能提供帮助,我宁愿不必中断查询 observables,感觉“hacky”。