私が取り組んでいるプロジェクトの 1 つで遭遇した 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>
この小さなアプリが行うことは、3 つのチェックボックス (最初はすべてオフ) と [適用] ボタンを表示することです。チェックボックスの状態が変わると、ボタンが有効になり、クリックすると、チェックボックスの状態が再び変わるまで無効になります。チェックボックスの状態を変更してから初期状態に戻すと、ボタンは適切に有効/無効になります。アプリは期待どおりに動作します。理由/方法を理解しようとしています。
今質問:
_initialState またはチェック ボックスが変更されるたびに、SelectMany 呼び出しがトリガーされますか?
_initialState.OnNext(initialState); の最初の呼び出し。(コンストラクターで) SelectMany コードに関しては、実際には何もしません。SelectMany コードに到達していることがわかりますが、実際には何も行われていません (つまり、checkBoxChanges.Select セクションにブレークポイントを設定すると、ブレークしますが、実際には何も選択されません)。これは、まだどのチェックボックスにも変更が加えられていないためですか?
予想どおり、checkBox をチェックすると isDirty チェックがトリガーされます。単一のチェックボックスを初めて変更したときに、この SelectMany ステートメントで何が起こっているのでしょうか?
チェックボックスをオンにすると、[適用] ボタンが有効になり、[適用] をクリックします。これにより、_initialState.OnNext(newState); が発生します。呼ばれること。私の最初の質問と同様に、SelectMany ステートメントでは何も起こらないようです。初期状態で新しい値を取得すると、何かが再計算されると思いましたが、 isDirty.Subscribe(d => IsDirty = d); の OnNext ハンドラーに直接行くようです。
Apply を押したところ、_initialState.OnNext が合計 2 回呼び出されました。新しいチェックボックスをオンにした場合、SelectMany はそれをどのように処理しますか? _initialState の過去のすべての状態を通過しますか? これらの値は、オブザーバブルが破棄されるまで保存されますか?
StartsWith/TakeUntil/Skip 行は何をしているのですか? TakeUntil 行を削除すると、SelectMany 句が _initialState の過去のすべての値を調べ始め、比較する実際の現在の状態がどれであるかについて混乱するため、アプリが正しく動作しなくなることに気付きました。
追加情報が必要な場合はお知らせください。