我有一些关于使用SelectMany的问题,我在其中一个项目中遇到过。下面是一个重现其用途的小样本(我使用了一些Console.WriteLines来帮助查看不同点的状态):
public partial class MainWindow : INotifyPropertyChanged
{
private bool _cb1, _cb2, _cb3, _isDirty;
private readonly ISubject<Unit> _cb1HasChanged = new Subject<Unit>();
private readonly ISubject<Unit> _cb2HasChanged = new Subject<Unit>();
private readonly ISubject<Unit> _cb3HasChanged = new Subject<Unit>();
private readonly ISubject<string> _initialState = new ReplaySubject<string>(1);
public MainWindow()
{
InitializeComponent();
DataContext = this;
ObserveCheckBoxes();
var initialState = string.Format("{0}{1}{2}", CB1, CB2, CB3);
_initialState.OnNext(initialState);
Console.WriteLine("INITIAL STATE: " + initialState);
}
public bool CB1
{
get
{
return _cb1;
}
set
{
_cb1 = value;
_cb1HasChanged.OnNext(Unit.Default);
}
}
public bool CB2
{
get
{
return _cb2;
}
set
{
_cb2 = value;
_cb2HasChanged.OnNext(Unit.Default);
}
}
public bool CB3
{
get
{
return _cb3;
}
set
{
_cb3 = value;
_cb3HasChanged.OnNext(Unit.Default);
}
}
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
private void ObserveCheckBoxes()
{
var checkBoxChanges = new[]
{
_cb1HasChanged,
_cb2HasChanged,
_cb3HasChanged
}
.Merge();
var isDirty = _initialState.SelectMany(initialState => checkBoxChanges
.Select(_ => GetNewState(initialState))
.Select(updatedState => initialState != updatedState)
.StartWith(false)
.TakeUntil(_initialState.Skip(1)));
isDirty.Subscribe(d => IsDirty = d);
}
private string GetNewState(string initialState = null)
{
string update = string.Format("{0}{1}{2}", CB1, CB2, CB3);
if (initialState != null)
{
Console.WriteLine("CREATING UPDATE: " + update + " INITIAL STATE: " + initialState);
}
else
{
Console.WriteLine("CREATING UPDATE: " + update);
}
return update;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
var newState = GetNewState();
_initialState.OnNext(newState);
Console.WriteLine("SAVED AS: " + newState);
}
}
和xaml:
<Window x:Class="WpfSB2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<CheckBox IsChecked="{Binding CB1}"></CheckBox>
<CheckBox IsChecked="{Binding CB2}"></CheckBox>
<CheckBox IsChecked="{Binding CB3}"></CheckBox>
<Button IsEnabled="{Binding IsDirty}" Click="Button_Click">APPLY</Button>
</StackPanel>
</Grid>
</Window>
所以这个小应用程序的作用是显示三个复选框(所有最初都未选中)和一个“应用”按钮。当复选框的状态发生变化时,该按钮应该变为启用状态,并且在单击时变为禁用状态,直到复选框的状态再次变化。如果您更改复选框的状态,然后将其更改回其初始状态,则该按钮将适当地启用/禁用。该应用程序按预期工作,我只是想弄清楚为什么/如何。
现在问题:
只要_initialState或复选框发生更改,是否会触发SelectMany调用?
_initialState.OnNext(initialState)的第一次调用; (在构造函数中)在SelectMany代码中并没有真正做任何事情。我看到它进入SelectMany代码,但实际上没有做任何事情(我的意思是,如果我在checkBoxChanges.Select部分放置一个断点,它会中断,但实际上没有选择)。这是因为还没有对任何复选框进行任何更改吗?
正如所料,检查任何checkBox会触发isDirty检查。第一次更改单个复选框时,SelectMany语句中究竟发生了什么?
选中一个复选框后,启用“应用”按钮,然后点击“应用”。这会导致_initialState.OnNext(newState);被称为。与我的第一个问题类似,SelectMany语句似乎没有发生任何事情。我认为初始状态获得一个新值会重新计算,但它似乎直接进入isDirty.Subscribe的OnNext处理程序(d =&gt; IsDirty = d);
现在我点击了Apply,_initialState.OnNext总共被调用了两次。如果我选中一个新复选框,SelectMany如何处理?它是否经历了_initialState的所有过去状态?这些值是否存储在可观察量被处理之前?
StartsWith / TakeUntil / Skip系列有哪些功能?我注意到,如果我删除了TakeUntil行,那么当SelectMany子句开始遍历_initialState的所有过去值时,应用程序会停止正常工作,并且会混淆哪个是要比较的实际当前状态。
如果您需要其他信息,请与我们联系。
答案 0 :(得分:2)
我认为您问题的关键部分是您对SelectMany的理解。如果您将SelectMany称为“从一个,选择多个”,我认为更容易理解。
对于源序列中的每个值,SelectMany将从另一个序列中提供零个,一个或多个值。
在您的情况下,您的源序列为_initialState
。每次从该序列产生一个值时,它将订阅所提供的“内部序列”。
直接回答你的问题:
1)当_initialState
推送一个值时,该值将传递给SelectMany运算符,并将订阅提供的“内部序列”。
2)第一个调用是将InitialState放在ReplaySubject的缓冲区中。这意味着当您第一次订阅_initialState
序列时,它将立即推送一个值。将您的断点放在GetNewState中将显示此工作。
3)当你选中一个复选框时,它将调用setter,它将OnNext _cbXHasChanged主题(yuck),它将流入Merged序列(checkBoxChanges),然后流入SelectMany委托查询。
4)在复选框推送新值(它们不是重放主题)之前不会发生任何事情
5-6)是的,你已经调用了两次,因此它将运行selectMany委托两次,但是当第二个“内部序列”被启动时,TakeUntil将终止第一个“内部序列”。
SelectMany章节中的(我的网站)IntroToRx.com详细介绍了这一点。